diff --git a/Makefile b/Makefile
index 00f5ac5..32182d5 100644
--- a/Makefile
+++ b/Makefile
@@ -39,6 +39,7 @@ SOURCES = src/mm.js \
src/ui.value.js \
src/ui.status.js \
src/ui.color.js \
+ src/ui.icon.js \
src/ui.help.js \
src/ui.io.js \
src/ui.backend.js \
diff --git a/css/shape.css b/css/shape.css
index 25add8c..aa21a84 100644
--- a/css/shape.css
+++ b/css/shape.css
@@ -36,3 +36,8 @@
border-width: 2px;
font-size: 120%;
}
+
+.item .icon {
+ margin: 0 0.5em 0 0;
+ font-size: x-large;
+}
diff --git a/css/ui.css b/css/ui.css
index d5e9cfe..b010bc5 100644
--- a/css/ui.css
+++ b/css/ui.css
@@ -191,3 +191,9 @@ button:not(:disabled) {
.ui#io button:nth-child(even) {
float: right;
}
+
+.ui select.fa-select ,
+.ui select.fa-select option {
+ font-family: fontAwesome;
+ font-size: x-large;
+}
diff --git a/examples/features.mymind b/examples/features.mymind
index c0325fa..cef13e6 100644
--- a/examples/features.mymind
+++ b/examples/features.mymind
@@ -1,214 +1,215 @@
{
- "root": {
- "id": "ujfdpxoz",
- "text": "My Mind
Features",
- "layout": "map",
- "children": [
- {
- "id": "cmetfpcz",
- "text": "Basic features",
- "side": "right",
- "color": "#e33",
- "children": [
- {
- "id": "oufzmqvy",
- "text": "Node selection, manipulation"
- },
- {
- "id": "hsthllrc",
- "text": "Rich text editing"
- },
- {
- "id": "spnaifjx",
- "text": "Auto-linking"
- },
- {
- "id": "meeftorp",
- "text": "Colors"
- },
- {
- "id": "pphsgpvu",
- "text": "Zooming"
- },
- {
- "id": "avzourxv",
- "text": "Shapes"
- },
- {
- "id": "bhszysqp",
- "text": "Full keyboard control"
- },
- {
- "id": "zcrefcfz",
- "text": "Folding",
- "collapsed": 1,
- "children": [
- {
- "id": "znuvtncs",
- "text": "Collapsing"
- },
- {
- "id": "dbbxxzzs",
- "text": "Expanding"
- }
- ]
- }
- ]
- },
- {
- "id": "nkrfhzwg",
- "text": "I/O",
- "side": "left",
- "color": "#3e3",
- "children": [
- {
- "id": "fpbtygky",
- "text": "Multiple backends",
- "children": [
- {
- "id": "ezwvmtko",
- "text": "Local storage"
- },
- {
- "id": "sczzbjgf",
- "text": "Local filesystem"
- },
- {
- "id": "megldbuw",
- "text": "Firebase",
- "children": [
- {
- "id": "krsvhker",
- "text": "Realtime sync"
- }
- ]
- },
- {
- "id": "rmxmrpum",
- "text": "Google Drive"
- },
- {
- "id": "eiocvrju",
- "text": "REST DAV-like"
- }
- ]
- },
- {
- "id": "bmatbsiz",
- "text": "Multiple file formats",
- "children": [
- {
- "id": "lyzyawpn",
- "text": "Native"
- },
- {
- "id": "chfwbjrp",
- "text": "Freemind"
- },
- {
- "id": "vhsldewc",
- "text": "Mind Architect"
- },
- {
- "id": "whmzheiy",
- "text": "MindMup"
- }
- ]
- },
- {
- "id": "hjialeal",
- "text": "Permalinks"
- }
- ]
- },
- {
- "id": "scoyihcg",
- "text": "Advanced features",
- "side": "right",
- "color": "#33e",
- "children": [
- {
- "id": "bvfjpmrm",
- "text": "Numerical node values",
- "value": "sum",
- "children": [
- {
- "id": "fppldjcr",
- "text": "Constant values",
- "value": 3.14
- },
- {
- "id": "lhqwyqwq",
- "text": "Functions",
- "value": 42
- }
- ]
- },
- {
- "id": "pjqwdjad",
- "text": "Bool node statuses",
- "value": "maybe",
- "status": "computed",
- "children": [
- {
- "id": "jsrxnhgy",
- "text": "Yes/No",
- "value": "yes",
- "status": "no"
- },
- {
- "id": "oqzwbzvf",
- "text": "Auto-propagation to parent",
- "value": "no",
- "status": "computed"
- }
- ]
- },
- {
- "id": "adqojbln",
- "text": "Variable layouts",
- "layout": "tree-right",
- "children": [
- {
- "id": "uglsvbbi",
- "text": "Map"
- },
- {
- "id": "mvqxmutu",
- "text": "Graph"
- },
- {
- "id": "uonrboqe",
- "text": "Tree"
- }
- ]
- },
- {
- "id": "rsruxymh",
- "text": "Infinite undo/redo"
- },
- {
- "id": "jaalsyvs",
- "text": "Export to image"
- }
- ]
- },
- {
- "id": "rbahwqul",
- "text": "Planned features",
- "side": "left",
- "color": "#d3d",
- "children": [
- {
- "id": "vcmxdsvj",
- "text": "Custom styles"
- },
- {
- "id": "bsddmwik",
- "text": "Custom icons"
- }
- ]
- }
- ]
- }
+ "root": {
+ "id": "ujfdpxoz",
+ "text": "My Mind
Features",
+ "layout": "map",
+ "children": [
+ {
+ "id": "cmetfpcz",
+ "text": "Basic features",
+ "side": "right",
+ "color": "#e33",
+ "children": [
+ {
+ "id": "oufzmqvy",
+ "text": "Node selection, manipulation"
+ },
+ {
+ "id": "hsthllrc",
+ "text": "Rich text editing"
+ },
+ {
+ "id": "spnaifjx",
+ "text": "Auto-linking"
+ },
+ {
+ "id": "meeftorp",
+ "text": "Colors"
+ },
+ {
+ "id": "pphsgpvu",
+ "text": "Zooming"
+ },
+ {
+ "id": "avzourxv",
+ "text": "Shapes"
+ },
+ {
+ "id": "bhszysqp",
+ "text": "Full keyboard control"
+ },
+ {
+ "id": "zcrefcfz",
+ "text": "Folding",
+ "collapsed": 1,
+ "children": [
+ {
+ "id": "znuvtncs",
+ "text": "Collapsing"
+ },
+ {
+ "id": "dbbxxzzs",
+ "text": "Expanding"
+ }
+ ]
+ },
+ {
+ "id": "pkbumzof",
+ "text": "Icons",
+ "icon": "fa-hand-o-right"
+ }
+ ]
+ },
+ {
+ "id": "nkrfhzwg",
+ "text": "I/O",
+ "side": "left",
+ "color": "#3e3",
+ "children": [
+ {
+ "id": "fpbtygky",
+ "text": "Multiple backends",
+ "children": [
+ {
+ "id": "ezwvmtko",
+ "text": "Local storage"
+ },
+ {
+ "id": "sczzbjgf",
+ "text": "Local filesystem"
+ },
+ {
+ "id": "megldbuw",
+ "text": "Firebase",
+ "children": [
+ {
+ "id": "krsvhker",
+ "text": "Realtime sync"
+ }
+ ]
+ },
+ {
+ "id": "rmxmrpum",
+ "text": "Google Drive"
+ },
+ {
+ "id": "eiocvrju",
+ "text": "REST DAV-like"
+ }
+ ]
+ },
+ {
+ "id": "bmatbsiz",
+ "text": "Multiple file formats",
+ "children": [
+ {
+ "id": "lyzyawpn",
+ "text": "Native"
+ },
+ {
+ "id": "chfwbjrp",
+ "text": "Freemind"
+ },
+ {
+ "id": "vhsldewc",
+ "text": "Mind Architect"
+ },
+ {
+ "id": "whmzheiy",
+ "text": "MindMup"
+ }
+ ]
+ },
+ {
+ "id": "hjialeal",
+ "text": "Permalinks"
+ }
+ ]
+ },
+ {
+ "id": "scoyihcg",
+ "text": "Advanced features",
+ "side": "right",
+ "color": "#33e",
+ "children": [
+ {
+ "id": "bvfjpmrm",
+ "text": "Numerical node values",
+ "value": "sum",
+ "children": [
+ {
+ "id": "fppldjcr",
+ "text": "Constant values",
+ "value": 3.14
+ },
+ {
+ "id": "lhqwyqwq",
+ "text": "Functions",
+ "value": 42
+ }
+ ]
+ },
+ {
+ "id": "pjqwdjad",
+ "text": "Bool node statuses",
+ "value": "maybe",
+ "status": "computed",
+ "children": [
+ {
+ "id": "jsrxnhgy",
+ "text": "Yes/No",
+ "value": "yes",
+ "status": "no"
+ },
+ {
+ "id": "oqzwbzvf",
+ "text": "Auto-propagation to parent",
+ "value": "no",
+ "status": "computed"
+ }
+ ]
+ },
+ {
+ "id": "adqojbln",
+ "text": "Variable layouts",
+ "layout": "tree-right",
+ "children": [
+ {
+ "id": "uglsvbbi",
+ "text": "Map"
+ },
+ {
+ "id": "mvqxmutu",
+ "text": "Graph"
+ },
+ {
+ "id": "uonrboqe",
+ "text": "Tree"
+ }
+ ]
+ },
+ {
+ "id": "rsruxymh",
+ "text": "Infinite undo/redo"
+ },
+ {
+ "id": "jaalsyvs",
+ "text": "Export to image"
+ }
+ ]
+ },
+ {
+ "id": "rbahwqul",
+ "text": "Planned features",
+ "side": "left",
+ "color": "#d3d",
+ "children": [
+ {
+ "id": "vcmxdsvj",
+ "text": "Custom styles"
+ }
+ ]
+ }
+ ]
+ }
}
diff --git a/index.html b/index.html
index 536326c..c6f723d 100644
--- a/index.html
+++ b/index.html
@@ -11,6 +11,7 @@
+
@@ -76,6 +77,799 @@ My Mind
+
+ Icons
+
+
+
diff --git a/my-mind.js b/my-mind.js
index 67001af..f6ec6a9 100644
--- a/my-mind.js
+++ b/my-mind.js
@@ -299,6 +299,7 @@ MM.Item = function() {
this._value = null;
this._status = null;
this._side = null; /* side preference */
+ this._icon = null;
this._id = MM.generateId();
this._oldText = "";
@@ -311,6 +312,7 @@ MM.Item = function() {
node: document.createElement("li"),
content: document.createElement("div"),
status: document.createElement("span"),
+ icon: document.createElement("span"),
value: document.createElement("span"),
text: document.createElement("div"),
children: document.createElement("ul"),
@@ -320,6 +322,7 @@ MM.Item = function() {
this._dom.node.classList.add("item");
this._dom.content.classList.add("content");
this._dom.status.classList.add("status");
+ this._dom.icon.classList.add("icon");
this._dom.value.classList.add("value");
this._dom.text.classList.add("text");
this._dom.toggle.classList.add("toggle");
@@ -358,6 +361,7 @@ MM.Item.prototype.toJSON = function() {
if (this._side) { data.side = this._side; }
if (this._color) { data.color = this._color; }
+ if (this._icon) { data.icon = this._icon; }
if (this._value) { data.value = this._value; }
if (this._status) { data.status = this._status; }
if (this._layout) { data.layout = this._layout.id; }
@@ -378,6 +382,7 @@ MM.Item.prototype.fromJSON = function(data) {
if (data.id) { this._id = data.id; }
if (data.side) { this._side = data.side; }
if (data.color) { this._color = data.color; }
+ if (data.icon) { this._icon = data.icon; }
if (data.value) { this._value = data.value; }
if (data.status) {
this._status = data.status;
@@ -409,6 +414,11 @@ MM.Item.prototype.mergeWith = function(data) {
dirty = 2;
}
+ if (this._icon != data.icon) {
+ this._icon = data.icon;
+ dirty = 1;
+ }
+
if (this._value != data.value) {
this._value = data.value;
dirty = 1;
@@ -492,6 +502,7 @@ MM.Item.prototype.update = function(doNotRecurse) {
}
this._updateStatus();
+ this._updateIcon();
this._updateValue();
this._dom.node.classList[this._collapsed ? "add" : "remove"]("collapsed");
@@ -563,6 +574,15 @@ MM.Item.prototype.getStatus = function() {
return this._status;
}
+MM.Item.prototype.setIcon = function(icon) {
+ this._icon = icon;
+ return this.update();
+}
+
+MM.Item.prototype.getIcon = function() {
+ return this._icon;
+}
+
MM.Item.prototype.getComputedStatus = function() {
return this._computed.status;
}
@@ -791,6 +811,21 @@ MM.Item.prototype._updateStatus = function() {
break;
}
}
+MM.Item.prototype._updateIcon = function() {
+ this._dom.icon.className = "icon";
+ this._dom.icon.style.display = "";
+
+ var icon = this._icon;
+ if (icon)
+ {
+ this._dom.icon.classList.add('fa');
+ this._dom.icon.classList.add(icon);
+ this._computed.icon = true;
+ } else {
+ this._computed.icon = null;
+ this._dom.icon.style.display = "none";
+ }
+}
MM.Item.prototype._updateValue = function() {
this._dom.value.style.display = "";
@@ -1397,6 +1432,19 @@ MM.Action.SetStatus.prototype.undo = function() {
this._item.setStatus(this._oldStatus);
}
+MM.Action.SetIcon = function(item, icon) {
+ this._item = item;
+ this._icon = icon;
+ this._oldIcon = item.getIcon();
+}
+MM.Action.SetIcon.prototype = Object.create(MM.Action.prototype);
+MM.Action.SetIcon.prototype.perform = function() {
+ this._item.setIcon(this._icon);
+}
+MM.Action.SetIcon.prototype.undo = function() {
+ this._item.setIcon(this._oldIcon);
+}
+
MM.Action.SetSide = function(item, side) {
this._item = item;
this._side = side;
@@ -2214,10 +2262,12 @@ MM.Layout._alignItem = function(item, side) {
switch (side) {
case "left":
+ dom.content.insertBefore(dom.icon, dom.content.firstChild);
dom.content.appendChild(dom.value);
dom.content.appendChild(dom.status);
break;
case "right":
+ dom.content.insertBefore(dom.icon, dom.content.firstChild);
dom.content.insertBefore(dom.value, dom.content.firstChild);
dom.content.insertBefore(dom.status, dom.content.firstChild);
break;
@@ -2977,6 +3027,7 @@ MM.Format.MMA._parseAttributes = function(node, parent) {
json.color = "#" + [r,g,b].join("");
}
+ json.icon = node.getAttribute("icon");
return json;
}
@@ -2994,6 +3045,9 @@ MM.Format.MMA._serializeAttributes = function(doc, json) {
var b = new Array(5).join(parts[3]);
elm.setAttribute("color", "#" + [r,g,b].join(""));
}
+ if (json.icon) {
+ elm.setAttribute("icon", json.icon);
+ }
return elm;
}
@@ -3024,7 +3078,8 @@ MM.Format.Mup._MupToMM = function(item) {
var json = {
text: MM.Format.nl2br(item.title),
id: item.id,
- shape: "box"
+ shape: "box",
+ icon: item.icon
}
if (item.attr && item.attr.style && item.attr.style.background) {
@@ -3061,6 +3116,7 @@ MM.Format.Mup._MMtoMup = function(item, side) {
var result = {
id: item.id,
title: MM.Format.br2nl(item.text),
+ icon: item.icon,
attr: {}
}
if (item.color) {
@@ -3702,6 +3758,7 @@ MM.UI = function() {
this._layout = new MM.UI.Layout();
this._shape = new MM.UI.Shape();
+ this._icon = new MM.UI.Icon();
this._color = new MM.UI.Color();
this._value = new MM.UI.Value();
this._status = new MM.UI.Status();
@@ -3767,6 +3824,7 @@ MM.UI.prototype.getWidth = function() {
MM.UI.prototype._update = function() {
this._layout.update();
this._shape.update();
+ this._icon.update();
this._value.update();
this._status.update();
}
@@ -3894,6 +3952,19 @@ MM.UI.Color.prototype.handleEvent = function(e) {
var action = new MM.Action.SetColor(MM.App.current, color);
MM.App.action(action);
}
+MM.UI.Icon = function() {
+ this._select = document.querySelector("#icons");
+ this._select.addEventListener("change", this);
+}
+
+MM.UI.Icon.prototype.update = function() {
+ this._select.value = MM.App.current.getIcon() || "";
+}
+
+MM.UI.Icon.prototype.handleEvent = function(e) {
+ var action = new MM.Action.SetIcon(MM.App.current, this._select.value || null);
+ MM.App.action(action);
+}
MM.UI.Help = function() {
this._node = document.querySelector("#help");
this._map = {
diff --git a/screenshot.png b/screenshot.png
index 59f8fb3..a152702 100644
Binary files a/screenshot.png and b/screenshot.png differ
diff --git a/src/action.js b/src/action.js
index 9880886..f3ec28b 100644
--- a/src/action.js
+++ b/src/action.js
@@ -187,6 +187,19 @@ MM.Action.SetStatus.prototype.undo = function() {
this._item.setStatus(this._oldStatus);
}
+MM.Action.SetIcon = function(item, icon) {
+ this._item = item;
+ this._icon = icon;
+ this._oldIcon = item.getIcon();
+}
+MM.Action.SetIcon.prototype = Object.create(MM.Action.prototype);
+MM.Action.SetIcon.prototype.perform = function() {
+ this._item.setIcon(this._icon);
+}
+MM.Action.SetIcon.prototype.undo = function() {
+ this._item.setIcon(this._oldIcon);
+}
+
MM.Action.SetSide = function(item, side) {
this._item = item;
this._side = side;
diff --git a/src/format.mma.js b/src/format.mma.js
index 08e3d9d..a5c50cc 100644
--- a/src/format.mma.js
+++ b/src/format.mma.js
@@ -31,6 +31,7 @@ MM.Format.MMA._parseAttributes = function(node, parent) {
json.color = "#" + [r,g,b].join("");
}
+ json.icon = node.getAttribute("icon");
return json;
}
@@ -48,6 +49,9 @@ MM.Format.MMA._serializeAttributes = function(doc, json) {
var b = new Array(5).join(parts[3]);
elm.setAttribute("color", "#" + [r,g,b].join(""));
}
+ if (json.icon) {
+ elm.setAttribute("icon", json.icon);
+ }
return elm;
}
diff --git a/src/format.mup.js b/src/format.mup.js
index 68d04e8..880bbae 100644
--- a/src/format.mup.js
+++ b/src/format.mup.js
@@ -25,7 +25,8 @@ MM.Format.Mup._MupToMM = function(item) {
var json = {
text: MM.Format.nl2br(item.title),
id: item.id,
- shape: "box"
+ shape: "box",
+ icon: item.icon
}
if (item.attr && item.attr.style && item.attr.style.background) {
@@ -62,6 +63,7 @@ MM.Format.Mup._MMtoMup = function(item, side) {
var result = {
id: item.id,
title: MM.Format.br2nl(item.text),
+ icon: item.icon,
attr: {}
}
if (item.color) {
diff --git a/src/item.js b/src/item.js
index 8452289..6ff477d 100644
--- a/src/item.js
+++ b/src/item.js
@@ -10,6 +10,7 @@ MM.Item = function() {
this._value = null;
this._status = null;
this._side = null; /* side preference */
+ this._icon = null;
this._id = MM.generateId();
this._oldText = "";
@@ -22,6 +23,7 @@ MM.Item = function() {
node: document.createElement("li"),
content: document.createElement("div"),
status: document.createElement("span"),
+ icon: document.createElement("span"),
value: document.createElement("span"),
text: document.createElement("div"),
children: document.createElement("ul"),
@@ -31,6 +33,7 @@ MM.Item = function() {
this._dom.node.classList.add("item");
this._dom.content.classList.add("content");
this._dom.status.classList.add("status");
+ this._dom.icon.classList.add("icon");
this._dom.value.classList.add("value");
this._dom.text.classList.add("text");
this._dom.toggle.classList.add("toggle");
@@ -69,6 +72,7 @@ MM.Item.prototype.toJSON = function() {
if (this._side) { data.side = this._side; }
if (this._color) { data.color = this._color; }
+ if (this._icon) { data.icon = this._icon; }
if (this._value) { data.value = this._value; }
if (this._status) { data.status = this._status; }
if (this._layout) { data.layout = this._layout.id; }
@@ -89,6 +93,7 @@ MM.Item.prototype.fromJSON = function(data) {
if (data.id) { this._id = data.id; }
if (data.side) { this._side = data.side; }
if (data.color) { this._color = data.color; }
+ if (data.icon) { this._icon = data.icon; }
if (data.value) { this._value = data.value; }
if (data.status) {
this._status = data.status;
@@ -120,6 +125,11 @@ MM.Item.prototype.mergeWith = function(data) {
dirty = 2;
}
+ if (this._icon != data.icon) {
+ this._icon = data.icon;
+ dirty = 1;
+ }
+
if (this._value != data.value) {
this._value = data.value;
dirty = 1;
@@ -203,6 +213,7 @@ MM.Item.prototype.update = function(doNotRecurse) {
}
this._updateStatus();
+ this._updateIcon();
this._updateValue();
this._dom.node.classList[this._collapsed ? "add" : "remove"]("collapsed");
@@ -274,6 +285,15 @@ MM.Item.prototype.getStatus = function() {
return this._status;
}
+MM.Item.prototype.setIcon = function(icon) {
+ this._icon = icon;
+ return this.update();
+}
+
+MM.Item.prototype.getIcon = function() {
+ return this._icon;
+}
+
MM.Item.prototype.getComputedStatus = function() {
return this._computed.status;
}
@@ -502,6 +522,21 @@ MM.Item.prototype._updateStatus = function() {
break;
}
}
+MM.Item.prototype._updateIcon = function() {
+ this._dom.icon.className = "icon";
+ this._dom.icon.style.display = "";
+
+ var icon = this._icon;
+ if (icon)
+ {
+ this._dom.icon.classList.add('fa');
+ this._dom.icon.classList.add(icon);
+ this._computed.icon = true;
+ } else {
+ this._computed.icon = null;
+ this._dom.icon.style.display = "none";
+ }
+}
MM.Item.prototype._updateValue = function() {
this._dom.value.style.display = "";
diff --git a/src/layout.js b/src/layout.js
index b0644c7..8eec5aa 100644
--- a/src/layout.js
+++ b/src/layout.js
@@ -136,10 +136,12 @@ MM.Layout._alignItem = function(item, side) {
switch (side) {
case "left":
+ dom.content.insertBefore(dom.icon, dom.content.firstChild);
dom.content.appendChild(dom.value);
dom.content.appendChild(dom.status);
break;
case "right":
+ dom.content.insertBefore(dom.icon, dom.content.firstChild);
dom.content.insertBefore(dom.value, dom.content.firstChild);
dom.content.insertBefore(dom.status, dom.content.firstChild);
break;
diff --git a/src/ui.icon.js b/src/ui.icon.js
new file mode 100644
index 0000000..3f01ed3
--- /dev/null
+++ b/src/ui.icon.js
@@ -0,0 +1,13 @@
+MM.UI.Icon = function() {
+ this._select = document.querySelector("#icons");
+ this._select.addEventListener("change", this);
+}
+
+MM.UI.Icon.prototype.update = function() {
+ this._select.value = MM.App.current.getIcon() || "";
+}
+
+MM.UI.Icon.prototype.handleEvent = function(e) {
+ var action = new MM.Action.SetIcon(MM.App.current, this._select.value || null);
+ MM.App.action(action);
+}
diff --git a/src/ui.js b/src/ui.js
index c649d7c..6f11eef 100644
--- a/src/ui.js
+++ b/src/ui.js
@@ -5,6 +5,7 @@ MM.UI = function() {
this._layout = new MM.UI.Layout();
this._shape = new MM.UI.Shape();
+ this._icon = new MM.UI.Icon();
this._color = new MM.UI.Color();
this._value = new MM.UI.Value();
this._status = new MM.UI.Status();
@@ -70,6 +71,7 @@ MM.UI.prototype.getWidth = function() {
MM.UI.prototype._update = function() {
this._layout.update();
this._shape.update();
+ this._icon.update();
this._value.update();
this._status.update();
}