Oops!
We can't seem to find the page you're looking for.
diff --git a/client/pages/viewerpage.js b/client/pages/viewerpage.js
index 42d0fe70..9821d901 100644
--- a/client/pages/viewerpage.js
+++ b/client/pages/viewerpage.js
@@ -2,6 +2,7 @@ import React from 'react';
import Path from 'path';
import './viewerpage.scss';
+import './error.scss';
import { Files } from '../model/';
import { BreadCrumb, Bundle, NgIf, Loader, Container, EventReceiver, EventEmitter } from '../components/';
import { debounce, opener, notify } from '../helpers/';
@@ -31,6 +32,7 @@ export class ViewerPage extends React.Component {
needSaving: false,
isSaving: false,
loading: true,
+ error: null
};
this.props.subscribe('file.select', this.onPathUpdate.bind(this));
}
@@ -58,7 +60,7 @@ export class ViewerPage extends React.Component {
if(err && err.code === 'BINARY_FILE'){
this.setState({opener: 'download', loading: false});
}else{
- notify.send(err, 'error');
+ this.setState({error: err});
}
});
}else{
@@ -114,7 +116,7 @@ export class ViewerPage extends React.Component {
-
+
-
+
+
+ Oops!
+ There is nothing in here
+ {JSON.stringify(this.state.error)}
+
);
diff --git a/client/pages/viewerpage/editor.js b/client/pages/viewerpage/editor.js
index e95b52d7..8376d583 100644
--- a/client/pages/viewerpage/editor.js
+++ b/client/pages/viewerpage/editor.js
@@ -30,9 +30,11 @@ export class Editor extends React.Component {
this.state = {
loading: null,
editor: null,
- filename: this.props.filename
+ filename: this.props.filename,
+ listeners: []
};
this._refresh = this._refresh.bind(this);
+ this.onEdit = this.onEdit.bind(this);
}
_refresh(){
@@ -56,12 +58,18 @@ export class Editor extends React.Component {
const [type, value] = data;
if(type === "goTo"){
const pY = this.state.editor.charCoords({line: value, ch: 0}, "local").top;
- this.state.editor.scrollTo(null, pY);
- //this.state.editor.setCursor({line: new_props.currentLine, ch: 2});
+ this.state.editor.operation((cm) => {
+ this.state.editor.scrollTo(null, pY);
+ this.state.editor.setSelection({line: value, ch: 0}, {line: value, ch: this.state.editor.getLine(value).length});
+ });
}else if(type === "refresh"){
const cursor = this.state.editor.getCursor();
+ const selections = this.state.editor.listSelections();
this.state.editor.setValue(this.props.content);
this.state.editor.setCursor(cursor);
+ if(selections.length > 0){
+ this.state.editor.setSelection(selections[0].anchor, selections[0].head);
+ }
}else if(type === "fold"){
this.props.onFoldChange(
org_shifttab(this.state.editor)
@@ -74,7 +82,7 @@ export class Editor extends React.Component {
function loadCodeMirror(data){
const [CodeMirror, mode] = data;
- const size_small = 500;
+ let listeners = [];
let editor = CodeMirror(document.getElementById('editor'), {
value: this.props.content,
lineNumbers: true,
@@ -85,26 +93,20 @@ export class Editor extends React.Component {
widget: "..."
}
});
-
if(!('ontouchstart' in window)) editor.focus();
+ editor.getWrapperElement().setAttribute("mode", mode);
+ this.props.onModeChange(mode);
+ editor.on('change', this.onEdit);
if(mode === "orgmode"){
- CodeMirror.orgmode.init(editor, (key, value) => {
+ listeners.push(CodeMirror.orgmode.init(editor, (key, value) => {
if(key === "shifttab"){
this.props.onFoldChange(value);
}
- });
+ }));
}
- this.setState({editor: editor});
- this.props.onModeChange(mode);
-
- editor.on('change', (edit) => {
- if(this.props.onChange){
- this.props.onChange(edit.getValue());
- }
- });
CodeMirror.commands.save = () => {
this.props.onSave && this.props.onSave();
@@ -114,12 +116,24 @@ export class Editor extends React.Component {
window.history.back();
}
});
+
+ return new Promise((done) => {
+ this.setState({editor: editor, listeners: listeners}, done);
+ });
+ }
+ }
+
+ onEdit(cm){
+ if(this.props.onChange){
+ this.props.onChange(cm.getValue());
}
}
componentWillUnmount(){
window.removeEventListener('resize', this._refresh);
+ this.state.editor.off('change', this.onEdit);
this.state.editor.clearHistory();
+ this.state.listeners.map((fn) => fn());
}
loadMode(file){
diff --git a/client/pages/viewerpage/editor.scss b/client/pages/viewerpage/editor.scss
index dcbe045b..a5f8a142 100644
--- a/client/pages/viewerpage/editor.scss
+++ b/client/pages/viewerpage/editor.scss
@@ -19,10 +19,8 @@
height: 100%;
color: #3b4045;
background: var(--bg-color);
- font-size: 16px;
- font-family: 'Inconsolata', monospace;
-
}
+
.CodeMirror-sizer{
> div{
padding-top: 4px;
@@ -36,12 +34,14 @@
}
/* HIDE LINE NUMBERS ON MOBILE */
-// this hack is important as we rely on the dom to provide code folding for org mode
@media screen and (max-width: 400px) {
.CodeMirror-sizer{ margin-left: 0!important; }
.CodeMirror-gutters{ display: none; }
.CodeMirror-gutter-wrapper{ display: none; }
}
+.CodeMirror-linenumber{
+ cursor: pointer;
+}
/* SEARCH */
@@ -71,44 +71,82 @@
background: transparent;
width: 20em;
color: white;
- font-family: monospace;
}
.CodeMirror-dialog button {
font-size: 70%;
}
-/* Highlight Theme */
+/* Font stuff */
+.CodeMirror {
+ font-size: 15px;
+ font-family: 'Source Code Pro', monospace;
+}
+.cm-s-default .cm-header { font-size: 17px; }
+.cm-s-default .cm-header.cm-level1{ font-size: 18px;}
+
+@media only screen and (max-width: 600px) {
+ .CodeMirror { font-size: 14px; }
+ .cm-s-default .cm-header { font-size: 15px; }
+ .cm-s-default .cm-header.cm-level1{ font-size: 16px;}
+}
+
+/* Make things more confy */
+.CodeMirror{
+ .CodeMirror-code{ line-height: 1.3em; }
+ &[mode="orgmode"] .CodeMirror-code, &[mode="yaml-frontmatter"] .CodeMirror-code{
+ line-height: 1.5em;
+ }
+
+}
+
+/* Widget stuff */
+.CodeMirror-linewidget{
+ img{
+ margin: 10px;
+ height: 300px;
+ max-width: 80%;
+ text-align: center;
+ box-shadow: 1px 1px 5px rgba(0,0,0,0.5);
+ background: var(--dark);
+ }
+}
+
+/* Code Highlight Theme */
.cm-s-default .cm-header {
color: #3E7AA6;
- font-size: 18px;
- margin: 1px 0 1px 0;
- display: inline-block;
- font-kerning: normal;
line-height: 1em;
+ font-weight: 600;
}
-.cm-s-default .cm-keyword {color: #3E7AA6;}
+.cm-header.cm-level1{ color: #376e95; }
+.cm-s-default .cm-keyword { color: var(--emphasis-secondary); }
.cm-s-default .cm-header.cm-org-level-star{
color: #6f6f6f;
- vertical-align: text-bottom;
+ vertical-align: baseline;
display: inline-block;
- padding-left: 4px;
- margin-left: -4px;
+ padding-left: 5px;
+ margin-left: -5px;
+ cursor: pointer;
}
-.cm-s-default .cm-header.cm-org-todo{color: #FF8355; font-weight: normal;}
-.cm-s-default .cm-header.cm-org-done{color: #3BB27C; font-weight: normal;}
+.cm-s-default .cm-header.cm-org-todo{ color: #FF8355; font-weight: normal; cursor: pointer; }
+.cm-s-default .cm-header.cm-org-done{ color: #3BB27C; font-weight: normal; cursor: pointer; }
+.cm-s-default .cm-header.cm-org-priority{ cursor: pointer; font-weight: normal; }
+.cm-s-default .cm-org-toggle{ cursor: pointer; }
.cm-s-default .cm-void {
display: inline-block;
- max-width: 20px;
+ max-width: 10px;
overflow: hidden;
white-space: nowrap;
}
-.cm-s-default .cm-header.cm-comment{font-weight: normal;}
+.cm-s-default .cm-header.cm-comment{font-weight: normal; font-size: 0.9em!important; float: right; display: inline-block; color: var(--secondary);}
+pre.CodeMirror-line{clear: right;}
-.cm-s-default .cm-link{color: #555!important;}
-.cm-s-default .cm-url{color: #555!important;}
+
+.cm-s-default .cm-link{color: var(--secondary)}
+.cm-s-default .cm-strong{color: var(--secondary)}
+.cm-s-default .cm-org-url, .cm-s-default .cm-org-image{color: var(--secondary)!important; border-bottom: 1px dashed var(--light); cursor: pointer;}
.cm-s-default .cm-variable-3 {color: #085;}
-.cm-s-default .cm-comment {color: #6f6f6f;}
+.cm-s-default .cm-comment {color: var(--secondary);}
.cm-s-default .cm-string, .cm-s-default .cm-string-2{ color: #c41a16; }
.cm-s-default .cm-def { color: rgb(68, 85, 136); }
@@ -121,11 +159,13 @@
color: var(--color);
text-shadow: 1px 1px 10px var(--color);
}
+
span.CodeMirror-matchingbracket {color: #0f0;}
span.CodeMirror-nonmatchingbracket {color: #f22;}
+
/* BUGFIX */
// https://github.com/codemirror/CodeMirror/issues/5056
.CodeMirror-cursor {
- width: 1px !important;
+ min-width: 1px !important;
}
diff --git a/client/pages/viewerpage/editor/emacs-org.js b/client/pages/viewerpage/editor/emacs-org.js
index 441eea56..856f9f70 100644
--- a/client/pages/viewerpage/editor/emacs-org.js
+++ b/client/pages/viewerpage/editor/emacs-org.js
@@ -38,6 +38,8 @@ function set_folding_mode(cm, mode){
}else if(mode === "CONTENT"){
folding_mode_content(cm);
}
+ cm.refresh();
+
function folding_mode_overview(cm){
cm.operation(function() {
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++){
diff --git a/client/pages/viewerpage/editor/orgmode.js b/client/pages/viewerpage/editor/orgmode.js
index 34b7ebbf..23908a3c 100644
--- a/client/pages/viewerpage/editor/orgmode.js
+++ b/client/pages/viewerpage/editor/orgmode.js
@@ -4,11 +4,14 @@ import {
org_metadown, org_insert_todo_heading, org_shiftleft, org_shiftright, fold, unfold,
isFold, org_set_fold, org_shiftmetaleft, org_shiftmetaright
} from './emacs-org';
+import { pathBuilder, dirname } from '../../../helpers/';
+let CodeMirror = window.CodeMirror;
CodeMirror.__mode = 'orgmode';
CodeMirror.defineSimpleMode("orgmode", {
start: [
+ {regex: /^(\*\s)(TODO|DOING|WAITING|NEXT|)(CANCELLED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, token: ["header level1 org-level-star","header level1 org-todo","header level1 org-done", "header level1 org-priority", "header level1", "header level1 void", "header level1 comment"]},
{regex: /^(\*{1,}\s)(TODO|DOING|WAITING|NEXT|)(CANCELLED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, token: ["header org-level-star","header org-todo","header org-done", "header org-priority", "header", "header void", "header comment"]},
{regex: /(\+[^\+]+\+)/, token: ["strikethrough"]},
{regex: /(\*[^\*]+\*)/, token: ["strong"]},
@@ -16,8 +19,9 @@ CodeMirror.defineSimpleMode("orgmode", {
{regex: /(\_[^\_]+\_)/, token: ["link"]},
{regex: /(\~[^\~]+\~)/, token: ["comment"]},
{regex: /(\=[^\=]+\=)/, token: ["comment"]},
- {regex: /\[\[[^\[\]]*\]\[[^\[\]]*\]\]/, token: "url"}, // links
- {regex: /\[[xX\s\-\_]?\]/, token: 'qualifier org-toggle'}, // checkbox
+ {regex: /\[\[[^\[\]]+\]\[[^\[\]]+\]\]/, token: "org-url"}, // links
+ {regex: /\!\[\[[^\[\]]+\]\]/, token: "org-image"}, // image
+ {regex: /\[[xX\s\-\_]\]/, token: 'qualifier org-toggle'}, // checkbox
{regex: /\#\+BEGIN_[A-Z]*/, token: "comment", next: "env"}, // comments
{regex: /:?[A-Z_]+\:.*/, token: "comment"}, // property drawers
{regex: /(\#\+[A-Z_]*)(\:.*)/, token: ["keyword", 'qualifier']}, // environments
@@ -73,9 +77,10 @@ CodeMirror.registerGlobalHelper("fold", "drawer", function(mode) {
while(end < lastLine){
end += 1;
if(isEndOfADrawer(end)){
- break
+ break;
}
}
+
return {
from: CodeMirror.Pos(start.line, cm.getLine(start.line).length),
to: CodeMirror.Pos(end, cm.getLine(end).length)
@@ -96,7 +101,6 @@ CodeMirror.registerGlobalHelper("fold", "drawer", function(mode) {
});
-
CodeMirror.registerHelper("orgmode", "init", (editor, fn) => {
editor.setOption("extraKeys", {
"Tab": function(cm) { org_cycle(cm); },
@@ -110,82 +114,13 @@ CodeMirror.registerHelper("orgmode", "init", (editor, fn) => {
"Shift-Alt-Right": function(cm){ org_shiftmetaright(cm); },
"Shift-Alt-Enter": function(cm){ org_insert_todo_heading(cm); },
"Shift-Left": function(cm){ org_shiftleft(cm); },
- "Shift-Right": function(cm){ org_shiftright(cm); },
+ "Shift-Right": function(cm){ org_shiftright(cm); }
});
fn('shifttab', org_set_fold(editor));
- editor.addKeyMap({
- "Ctrl-X Ctrl-C": function(cm){
- cm.execCommand('quit');
- }
- });
-
-
- // Toggle headline on org mode by clicking on the heading ;)
editor.on('mousedown', toggleHandler);
editor.on('touchstart', toggleHandler);
- function toggleHandler(cm, e){
- const position = cm.coordsChar({
- left: e.clientX || (e.targetTouches && e.targetTouches[0].clientX),
- top: e.clientY || (e.targetTouches && e.targetTouches[0].clientY)
- }, "page"),
- token = cm.getTokenAt(position);
-
- if(/org-level-star/.test(token.type)){
- _preventIfShould();
- _foldHeadline();
- }else if(/org-toggle/.test(token.type)){
- _preventIfShould();
- _toggleCheckbox();
- }else if(/org-todo/.test(token.type)){
- _preventIfShould();
- _toggleTodo();
- }else if(/org-done/.test(token.type)){
- _preventIfShould();
- _toggleDone();
- }else if(/org-priority/.test(token.type)){
- _preventIfShould();
- _togglePriority();
- }
-
-
- function _preventIfShould(){
- if('ontouchstart' in window) e.preventDefault();
- }
-
- function _foldHeadline(){
- const line = position.line;
- if(line >= 0){
- const cursor = {line: line, ch: 0};
- isFold(cm, cursor) ? unfold(cm, cursor) : fold(cm, cursor);
- }
- }
- function _toggleCheckbox(){
- const line = position.line;
- const content = cm.getRange({line: line, ch: token.start}, {line: line, ch: token.end});
- let new_content = content === "[X]" || content === "[x]" ? "[ ]" : "[X]";
- cm.replaceRange(new_content, {line: line, ch: token.start}, {line: line, ch: token.end});
- }
- function _toggleTodo(){
- const line = position.line;
- cm.replaceRange("DONE", {line: line, ch: token.start}, {line: line, ch: token.end});
- }
- function _toggleDone(){
- const line = position.line;
- cm.replaceRange("TODO", {line: line, ch: token.start}, {line: line, ch: token.end});
- }
- function _togglePriority(){
- const PRIORITIES = [" [#A] ", " [#B] ", " [#C] ", " [#A] "];
- const line = position.line;
- const content = cm.getRange({line: line, ch: token.start}, {line: line, ch: token.end});
- let new_content = PRIORITIES[PRIORITIES.indexOf(content) + 1];
- cm.replaceRange(new_content, {line: line, ch: token.start}, {line: line, ch: token.end});
- }
- }
- editor.on('gutterClick', function(cm, line){
- const cursor = {line: line, ch: 0};
- isFold(cm, cursor) ? unfold(cm, cursor) : fold(cm, cursor);
- });
+ editor.on('gutterClick', foldLine);
// fold everything except headers by default
editor.operation(function() {
@@ -195,6 +130,153 @@ CodeMirror.registerHelper("orgmode", "init", (editor, fn) => {
}
}
});
+ return CodeMirror.orgmode.destroy.bind(this, editor);
});
+CodeMirror.registerHelper("orgmode", "destroy", (editor) => {
+ editor.off('mousedown', toggleHandler);
+ editor.off('touchstart', toggleHandler);
+ editor.off('gutterClick', foldLine);
+});
+
+function foldLine(cm, line){
+ const cursor = {line: line, ch: 0};
+ isFold(cm, cursor) ? unfold(cm, cursor) : fold(cm, cursor);
+}
+
+
+let widgets = [];
+function toggleHandler(cm, e){
+ const position = cm.coordsChar({
+ left: e.clientX || (e.targetTouches && e.targetTouches[0].clientX),
+ top: e.clientY || (e.targetTouches && e.targetTouches[0].clientY)
+ }, "page"),
+ token = cm.getTokenAt(position);
+
+ _disableSelection();
+ if(/org-level-star/.test(token.type)){
+ _preventIfShould();
+ _foldHeadline();
+ _disableSelection();
+ }else if(/org-toggle/.test(token.type)){
+ _preventIfShould();
+ _toggleCheckbox();
+ _disableSelection();
+ }else if(/org-todo/.test(token.type)){
+ _preventIfShould();
+ _toggleTodo();
+ _disableSelection();
+ }else if(/org-done/.test(token.type)){
+ _preventIfShould();
+ _toggleDone();
+ _disableSelection();
+ }else if(/org-priority/.test(token.type)){
+ _preventIfShould();
+ _togglePriority();
+ _disableSelection();
+ }else if(/org-url/.test(token.type)){
+ _disableSelection();
+ _navigateLink();
+ }else if(/org-image/.test(token.type)){
+ _disableSelection();
+ _toggleImageWidget();
+ }
+
+ function _preventIfShould(){
+ if('ontouchstart' in window) e.preventDefault();
+ }
+ function _disableSelection(){
+ cm.on('beforeSelectionChange', _onSelectionChangeHandler);
+ function _onSelectionChangeHandler(cm, obj){
+ obj.update([{
+ anchor: position,
+ head: position
+ }]);
+ cm.off('beforeSelectionChange', _onSelectionChangeHandler);
+ }
+ }
+
+ function _foldHeadline(){
+ const line = position.line;
+ if(line >= 0){
+ const cursor = {line: line, ch: 0};
+ isFold(cm, cursor) ? unfold(cm, cursor) : fold(cm, cursor);
+ }
+ }
+
+ function _toggleCheckbox(){
+ const line = position.line;
+ const content = cm.getRange({line: line, ch: token.start}, {line: line, ch: token.end});
+ let new_content = content === "[X]" || content === "[x]" ? "[ ]" : "[X]";
+ cm.replaceRange(new_content, {line: line, ch: token.start}, {line: line, ch: token.end});
+ }
+
+ function _toggleTodo(){
+ const line = position.line;
+ cm.replaceRange("DONE", {line: line, ch: token.start}, {line: line, ch: token.end});
+ }
+
+ function _toggleDone(){
+ const line = position.line;
+ cm.replaceRange("TODO", {line: line, ch: token.start}, {line: line, ch: token.end});
+ }
+
+ function _togglePriority(){
+ const PRIORITIES = [" [#A] ", " [#B] ", " [#C] ", " [#A] "];
+ const line = position.line;
+ const content = cm.getRange({line: line, ch: token.start}, {line: line, ch: token.end});
+ let new_content = PRIORITIES[PRIORITIES.indexOf(content) + 1];
+ cm.replaceRange(new_content, {line: line, ch: token.start}, {line: line, ch: token.end});
+ }
+
+ function _toggleImageWidget(){
+ let exist = !!widgets
+ .filter((line) => line === position.line)[0];
+
+ if(exist === false){
+ if(!token.string.match(/\!\[\[(.*)\]\]/)) return null;
+ let $node = _buildImage(RegExp.$1);
+ const widget = cm.addLineWidget(position.line, $node, {coverGutter: false});
+ widgets.push(position.line);
+ $node.addEventListener('click', closeWidget);
+
+ function closeWidget(){
+ widget.clear();
+ $node.removeEventListener('click', closeWidget);
+ widgets = widgets.filter((line) => line !== position.line);
+ }
+ }
+ function _buildImage(src){
+ let $el = document.createElement("div");
+ let $img = document.createElement("img");
+
+ if(/^https?\:\/\//.test(src)){
+ $img.src = src;
+ }else{
+ const root_path = dirname(window.location.pathname.replace(/^\/view/, ''));
+ const img_path = src;
+ $img.src = "/api/files/cat?path="+encodeURIComponent(pathBuilder(root_path, img_path));
+ }
+ $el.appendChild($img);
+ return $el;
+ }
+ return null;
+ }
+
+ function _navigateLink(){
+ token.string.match(/\[\[(.*?)\]\[/);
+ const link = RegExp.$1;
+ if(!link) return;
+
+ if(/^https?\:\/\//.test(link)){
+ window.open(link);
+ }else{
+ const root_path = dirname(window.location.pathname.replace(/^\/view/, ''));
+ const link_path = link;
+ window.open("/view"+pathBuilder(root_path, link_path));
+ }
+ }
+}
+
+
export default CodeMirror;
diff --git a/client/pages/viewerpage/ide.js b/client/pages/viewerpage/ide.js
index b4da2089..c2c79810 100644
--- a/client/pages/viewerpage/ide.js
+++ b/client/pages/viewerpage/ide.js
@@ -54,11 +54,11 @@ export class IDE extends React.Component {
/* Org Viewer specific stuff */
- toggleAgenda(){
- this.setState({appear_agenda: !this.state.appear_agenda});
+ toggleAgenda(force = null){
+ this.setState({appear_agenda: force === null ? !this.state.appear_agenda : !!force});
}
- toggleTodo(){
- this.setState({appear_todo: !this.state.appear_todo});
+ toggleTodo(force = null){
+ this.setState({appear_todo: force === null ? !this.state.appear_todo : !!force});
}
onModeChange(){
this.state.event.next(["fold"]);
@@ -115,10 +115,10 @@ export class IDE extends React.Component {
+ onQuit={this.toggleAgenda.bind(this, false)} />
+ onQuit={this.toggleTodo.bind(this, false)} />
);
}
diff --git a/client/pages/viewerpage/menubar.scss b/client/pages/viewerpage/menubar.scss
index 3ba7b1b4..8f941202 100644
--- a/client/pages/viewerpage/menubar.scss
+++ b/client/pages/viewerpage/menubar.scss
@@ -28,7 +28,7 @@
height: 19px;
width: 19px;
cursor: pointer;
- padding: 6px 7px;
+ padding: 7px 7px 5px 7px
}
.download-button .component_icon{
padding-right: 1px;
diff --git a/client/pages/viewerpage/org_viewer.js b/client/pages/viewerpage/org_viewer.js
index be3a5055..513b1ab6 100644
--- a/client/pages/viewerpage/org_viewer.js
+++ b/client/pages/viewerpage/org_viewer.js
@@ -3,27 +3,40 @@ import { StickyContainer, Sticky } from 'react-sticky';
import { Modal, Container, NgIf, Icon, Dropdown, DropdownButton, DropdownList, DropdownItem } from '../../components/';
import { extractEvents, extractTodos } from '../../helpers/org';
+import { leftPad } from '../../helpers/common';
+import { debounce } from '../../helpers/';
import './org_viewer.scss';
-export const OrgEventsViewer = (props) => {
- if(props.isActive !== true) return null;
- const headlines = extractEvents(props.content);
+export class OrgEventsViewer extends React.Component {
+ shouldComponentUpdate(nextProps){
+ if(this.props.content !== nextProps.content) return true;
+ if(this.props.isActive !== nextProps.isActive) return true;
+ return false;
+ }
+ render(){
+ const headlines = this.props.isActive ? extractEvents(this.props.content) : [];
+ return (
+
- );
-};
+export class OrgTodosViewer extends React.Component {
+ shouldComponentUpdate(nextProps){
+ if(this.props.content !== nextProps.content) return true;
+ if(this.props.isActive !== nextProps.isActive) return true;
+ return false;
+ }
-export const OrgTodosViewer = (props) => {
- if(props.isActive !== true) return null;
- const headlines = extractTodos(props.content);
-
- return (
-
- );
-};
+ render(){
+ const headlines = this.props.isActive ? extractTodos(this.props.content) : [];
+ return (
+
+ );
+ }
+}
@@ -32,13 +45,18 @@ class OrgViewer extends React.Component {
super(props);
this.state = {
headlines: this.buildHeadlines(props.headlines),
- content: props.content
+ content: props.content,
+ search: '',
+ _: null
};
+ this.rerender = () => {this.setState({_: Math.random()});};
+ this.findResults = debounce(this.findResults.bind(this), 150);
}
componentWillReceiveProps(props){
this.setState({
- headlines: this.buildHeadlines(props.headlines)
+ headlines: this.buildHeadlines(props.headlines),
+ content: props.content
});
}
@@ -62,6 +80,7 @@ class OrgViewer extends React.Component {
onTaskUpdate(type, line, value){
const content = this.state.content.split("\n");
+ let head_line, item_line, head_status, deadline_line, scheduled_line, insertion_line;
switch(type){
case "status":
content[line] = content[line].replace(/^(\*+\s)[A-Z]{3,}(\s.*)$/, "$1"+value+"$2");
@@ -73,12 +92,109 @@ class OrgViewer extends React.Component {
content[line] = content[line].replace(/\[.\]/, '[ ]');
}
break;
- case "schedule":
+ case "existing_scheduled":
+ [head_line, head_status, item_line] = line;
+ content[item_line] = content[item_line].replace(/SCHEDULED\: \<.*?\>\s*/, value ? "SCHEDULED: "+orgdate(value)+" " : "");
+ this.state.headlines[head_status] = this.state.headlines[head_status]
+ .map((todo) => {
+ if(todo.line === head_line){
+ if(value) todo.scheduled.timestamp = new Date(value).toISOString();
+ else todo.scheduled = null;
+ }
+ return todo;
+ });
+ this.setState({headlines: this.state.headlines});
break;
- case "deadline":
+ case "existing_deadline":
+ [head_line, head_status, item_line] = line;
+ content[item_line] = content[item_line].replace(/DEADLINE\: \<.*?\>\s*/, value ? "DEADLINE: "+orgdate(value) : "");
+ this.state.headlines[head_status] = this.state.headlines[head_status]
+ .map((todo) => {
+ if(todo.line === head_line){
+ if(value) todo.deadline.timestamp = new Date(value).toISOString();
+ else todo.deadline = null;
+ }
+ return todo;
+ });
+ this.setState({headlines: this.state.headlines});
+ break;
+ case "new_scheduled":
+ [head_line, head_status, deadline_line] = line;
+ if(deadline_line !== null){
+ insertion_line = deadline_line;
+ content[deadline_line] = "SCHEDULED: "+orgdate(value)+" "+content[deadline_line];
+ }else{
+ insertion_line = head_line + 1;
+ if(content[insertion_line] === "" && content[insertion_line + 1] === ""){
+ content[insertion_line] = "SCHEDULED: "+orgdate(value);
+ }else{
+ content.splice(
+ insertion_line,
+ 0,
+ "SCHEDULED: "+orgdate(value)
+ );
+ }
+ }
+ this.state.headlines[head_status] = this.state.headlines[head_status]
+ .map((todo) => {
+ if(todo.line === head_line){
+ todo.scheduled = {
+ line: insertion_line,
+ keyword: "SCHEDULED",
+ active: true,
+ range: null,
+ repeat: null,
+ timestamp: new Date(value).toISOString()
+ };
+ }
+ return todo;
+ });
+ this.setState({headlines: this.state.headlines});
+ break;
+ case "new_deadline":
+ [head_line, head_status, scheduled_line] = line;
+ if(scheduled_line !== null){
+ insertion_line = scheduled_line;
+ content[scheduled_line] = content[scheduled_line]+" DEADLINE: "+orgdate(value);
+ }else{
+ insertion_line = head_line + 1;
+ if(content[insertion_line] === "" && content[insertion_line + 1] === ""){
+ content[insertion_line] = "DEADLINE: "+orgdate(value);
+ }else{
+ content.splice(
+ insertion_line,
+ 0,
+ "DEADLINE: "+orgdate(value)
+ );
+ }
+ this.state.headlines[head_status] = this.state.headlines[head_status]
+ .map((todo) => {
+ if(todo.line === head_line){
+ todo.deadline = {
+ line: insertion_line,
+ keyword: "DEADLINE",
+ active: true,
+ range: null,
+ repeat: null,
+ timestamp: new Date(value).toISOString()
+ };
+ }
+ return todo;
+ });
+ this.setState({headlines: this.state.headlines});
+ }
break;
};
this.setState({content: content.join("\n")});
+
+ function orgdate(_date){
+ const date = new Date(_date);
+ return "<"+date.getFullYear()+"-"+leftPad((date.getMonth() + 1).toString(), 2)+"-"+leftPad(date.getDate().toString(), 2)+" "+day(date.getDay())+">";
+
+ function day(n){
+ return ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][n];
+ }
+ }
}
navigate(line){
@@ -91,6 +207,41 @@ class OrgViewer extends React.Component {
this.props.onQuit();
}
+ componentDidMount(){
+ window.addEventListener('resize', this.rerender);
+ }
+ componentWillUnmount(){
+ window.removeEventListener('resize', this.rerender);
+ }
+
+ search(terms){
+ this.setState({search: terms}, () => {
+ this.findResults(terms);
+ });
+ }
+
+ findResults(terms){
+ let headlines = this.props.headlines;
+ if(terms){
+ headlines = this.props.headlines.filter((headline) => {
+ const keywords = terms.split(" ");
+ const head = function(){
+ let str = " ";
+ str += headline['status'] + " ";
+ str += headline['title'] + " ";
+ str += headline.tags.map((tag) => "#"+tag).join(" ") + " ";
+ str += headline.scheduled ? "scheduled "+headline.scheduled.timestamp + " ": "";
+ str += headline.deadline ? "deadline "+headline.deadline.timestamp + " ": "";
+ str += headline.priority ? "priority #"+headline.priority+" " : "";
+ str += headline.is_overdue ? "overdue " : "";
+ str += headline.tasks.map((task) => task.title).join(" ")+ " ";
+ return str;
+ }(headline);
+ return keywords.filter((keyword) => new RegExp(" "+keyword, "i").test(head)).length === keywords.length ? true : false;
+ });
+ }
+ this.setState({headlines: this.buildHeadlines(headlines)});
+ }
render(){
return (
@@ -100,6 +251,12 @@ class OrgViewer extends React.Component {