markuapad 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/src/jsx/main.jsx CHANGED
@@ -8,6 +8,7 @@ import LivePreview from "./live_preview";
8
8
  import FileAccessor from "../file_accessor";
9
9
  import ImageModal from "./image_modal";
10
10
  import _ from "underscore";
11
+ import MergeConflictModal from './merge_conflict_modal'
11
12
 
12
13
  import { getCached } from "../util"
13
14
 
@@ -21,27 +22,18 @@ class Main extends React.Component {
21
22
  imageFile: null,
22
23
  previewState: 'closed',
23
24
  inLiveMode: this.props.options.enablePreview,
24
- previewHtml: ""
25
+ previewHtml: "",
26
+ isResolvingMergeConflicts: false,
27
+ mergeConflicts: null
25
28
  }
26
29
 
27
- // Autobind
28
- this.onChangeFile = this.onChangeFile.bind(this);
29
- this.onGeneratePreview = this.onGeneratePreview.bind(this);
30
- this.onPreviewReady = this.onPreviewReady.bind(this);
31
- this.onClosePreview = this.onClosePreview.bind(this);
32
- this.toggleLiveMode = this.toggleLiveMode.bind(this);
33
- this.onBookContentChanged = this.onBookContentChanged.bind(this);
34
- this.getWorkspaceClass = this.getWorkspaceClass.bind(this);
35
- this.onManuscriptChange = this.onManuscriptChange.bind(this);
36
- this.onFileAdded = this.onFileAdded.bind(this);
37
- this.onPreviewImage = this.onPreviewImage.bind(this);
38
-
39
30
  // File access hooks
40
31
  FileAccessor.onDelete(this.onManuscriptChange);
41
32
  FileAccessor.onManuscriptChange(this.onManuscriptChange);
42
33
  FileAccessor.onAdd(this.onFileAdded);
43
34
  FileAccessor.onProgress(this.onProgress);
44
35
  FileAccessor.onProgressStarted(this.onProgressStarted);
36
+ FileAccessor.onMergeConflicts(this.onMergeConflicts);
45
37
  }
46
38
 
47
39
  // Lifecycle methods
@@ -51,12 +43,25 @@ class Main extends React.Component {
51
43
  this.onGeneratePreview();
52
44
  }
53
45
 
46
+ // parameter conflicts is an array of tuples.
47
+ // { filename: String, serverVersion: String, clientVersion: String }
48
+ onMergeConflicts = (conflicts) => {
49
+ this.setState({
50
+ mergeConflicts: conflicts,
51
+ isResolvingMergeConflicts: true
52
+ })
53
+ }
54
+
55
+ doneResolvingConflicts = () => {
56
+ this.setState({ mergeConflicts: null, isResolvingMergeConflicts: false })
57
+ }
58
+
54
59
  // File event operations
55
- onFileAdded(file) {
60
+ onFileAdded = (file) => {
56
61
  this.setState({ currentFile: file });
57
62
  }
58
63
 
59
- onManuscriptChange() {
64
+ onManuscriptChange = () => {
60
65
  // Re-preview
61
66
  if (this.state.inLiveMode)
62
67
  this.onGeneratePreview();
@@ -76,16 +81,16 @@ class Main extends React.Component {
76
81
  this.setState({ inProgress: true })
77
82
  }
78
83
 
79
- onPreviewImage(file) {
84
+ onPreviewImage = (file) => {
80
85
  this.setState({ imageFile: file });
81
86
  }
82
87
 
83
- onChangeFile(file) {
88
+ onChangeFile = (file) => {
84
89
  this.setState({ currentFile: file });
85
90
  }
86
91
 
87
92
  // Preview based methods
88
- onPreviewReady(errors, html) {
93
+ onPreviewReady = (errors, html) => {
89
94
  // If someone has already stopped the preview then just bail
90
95
  if (this.state.previewState === "closed")
91
96
  return
@@ -93,17 +98,17 @@ class Main extends React.Component {
93
98
  this.setState({ previewHtml: html, previewState: "done", previewErrors: errors })
94
99
  }
95
100
 
96
- onGeneratePreview(e) {
101
+ onGeneratePreview = (e) => {
97
102
  // Call out to the markua processor
98
103
  this.setState({ previewState: "previewing" });
99
104
  this.props.markua.run(this.onPreviewReady, { cursor: getCached("markuapad_cursor") })
100
105
  }
101
106
 
102
- onClosePreview(e) {
107
+ onClosePreview = (e) => {
103
108
  this.setState({ previewState: "closed" })
104
109
  }
105
110
 
106
- toggleLiveMode() {
111
+ toggleLiveMode = () => {
107
112
  // Clear the preview state here as well, so we don't accidentally open a preview
108
113
  this.setState({ inLiveMode: !this.state.inLiveMode, previewState: "closed" }, () => {
109
114
  window.dispatchEvent(new Event('resize'));
@@ -114,12 +119,12 @@ class Main extends React.Component {
114
119
  });
115
120
  }
116
121
 
117
- onBookContentChanged() {
122
+ onBookContentChanged = () => {
118
123
  if (this.state.inLiveMode)
119
124
  this.onGeneratePreview();
120
125
  }
121
126
 
122
- getWorkspaceClass() {
127
+ getWorkspaceClass = () => {
123
128
  let workspaceClass = `workspace`;
124
129
  workspaceClass += this.state.inLiveMode ? ' live' : '';
125
130
  return workspaceClass;
@@ -151,6 +156,7 @@ class Main extends React.Component {
151
156
  </section>
152
157
  { this.state.inLiveMode ? <span /> : <Preview key='preview' inLiveMode={this.state.inLiveMode} onClosePreview={this.onClosePreview} html={this.state.previewHtml} previewState={this.state.previewState} previewErrors={this.state.previewErrors} /> }
153
158
  { this.state.imageFile ? <ImageModal file={this.state.imageFile} onClose={ _.partial(this.onPreviewImage, null) } /> : null }
159
+ { this.state.isResolvingMergeConflicts ? <MergeConflictModal conflicts={this.state.mergeConflicts} onClose={this.doneResolvingConflicts} /> : null }
154
160
  </section>
155
161
  );
156
162
  }
@@ -0,0 +1,99 @@
1
+ import React from 'react';
2
+ import FileAccessor from "../file_accessor"
3
+ import _ from 'underscore'
4
+
5
+ const delimeter = (type, filename) => {
6
+ return `\n\n================================= ${type} version of ${filename} =================================\n\n`
7
+ }
8
+
9
+ class MergeConflictModal extends React.Component {
10
+ static propTypes = {
11
+ conflicts: React.PropTypes.array.isRequired,
12
+ onClose: React.PropTypes.func.isRequired
13
+ }
14
+
15
+ state = {
16
+ currentConflictIndex: 0
17
+ }
18
+
19
+ resolveConflict = (method) => {
20
+ let conflict = this.props.conflicts[this.state.currentConflictIndex];
21
+
22
+ switch (method) {
23
+ case 'client':
24
+ break;
25
+ case 'server':
26
+ // Save the file on the server.
27
+ FileAccessor.save(conflict.filename, 'manuscript', conflict.serverVersion)
28
+ break;
29
+ case 'both':
30
+ // Concat both versions separated by a delimeter
31
+ FileAccessor.save(conflict.filename, 'manuscript', delimeter('Local', conflict.filename) +
32
+ conflict.clientVersion +
33
+ delimeter('Server', conflict.filename) +
34
+ conflict.serverVersion)
35
+ break;
36
+ }
37
+
38
+ if (this.state.currentConflictIndex + 1 >= this.props.conflicts.length) {
39
+ // We are done.
40
+ this.props.onClose()
41
+ } else {
42
+ // Increase the index
43
+ this.setState({ currentConflictIndex: this.state.currentConflictIndex + 1 })
44
+ }
45
+ }
46
+
47
+ renderCurrentConflict() {
48
+ return (
49
+ <div className="conflict-wrapper">
50
+ <section className="conflict-content client-version">
51
+ <h6>Your local copy</h6>
52
+ <pre>
53
+ { this.props.conflicts[this.state.currentConflictIndex].clientVersion }
54
+ </pre>
55
+ </section>
56
+ <section className="conflict-content server-version">
57
+ <h6>Server copy</h6>
58
+ <pre>
59
+ { this.props.conflicts[this.state.currentConflictIndex].serverVersion }
60
+ </pre>
61
+ </section>
62
+ </div>
63
+ );
64
+ }
65
+
66
+ render() {
67
+ return (
68
+ <div className="modal merge-conflicts-modal">
69
+ <div className="modal-fade-screen">
70
+ <div className="modal-inner">
71
+ <div className="modal-close" onClick={ this.props.onClose }></div>
72
+ <h4>Conflicting changes for { this.props.conflicts[this.state.currentConflictIndex].filename }</h4>
73
+ <p className="conflict-explanation">
74
+ Uh oh! You have conflicting changes for { this.props.conflicts[this.state.currentConflictIndex].filename }. This
75
+ probably happened because you were using the writing tools without
76
+ an active internet connection. To resolve this, review the differences here, and select the option
77
+ you want at the bottom. If you choose to 'keep both', then we will combine the two versions into one file so you
78
+ can manually merge them.
79
+ </p>
80
+ { this.renderCurrentConflict() }
81
+ <ul className="conflict-actions">
82
+ <li>
83
+ <a onClick={_.partial(this.resolveConflict, 'client')}>Keep local version</a>
84
+ </li>
85
+ <li>
86
+ <a onClick={_.partial(this.resolveConflict, 'server')}>Keep remote version</a>
87
+ </li>
88
+ <li>
89
+ <a onClick={_.partial(this.resolveConflict, 'both')}>Keep both versions</a>
90
+ </li>
91
+ </ul>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ )
96
+ }
97
+ }
98
+
99
+ export default MergeConflictModal;
@@ -0,0 +1,51 @@
1
+ .markuapad .merge-conflicts-modal {
2
+ .modal-fade-screen {
3
+ padding-top: 4em;
4
+ }
5
+
6
+ .modal-inner {
7
+ max-height: 95%;
8
+ width: 80%;
9
+ }
10
+
11
+ .conflict-explanation {
12
+ padding: 1em;
13
+ background: #fcc;
14
+ border: thin solid #faa;
15
+ text-shadow: 0 1px 0 #fff;
16
+ border-radius: 4px;
17
+ }
18
+
19
+ .conflict-wrapper {
20
+ @include display(flex);
21
+ @include flex(1 1 100%);
22
+ @include flex-flow(row nowrap);
23
+ padding: 20px 0;
24
+ }
25
+
26
+ .conflict-content {
27
+ @include flex(1);
28
+ padding: 1em;
29
+
30
+ &.client-version {
31
+ border-right: thin dashed #ddd;
32
+ }
33
+
34
+ h6 {
35
+ border-bottom: thin dashed #ddd;
36
+ font-weight: 700;
37
+ padding-bottom: 1em;
38
+ }
39
+ }
40
+
41
+ .conflict-actions {
42
+ text-align: center;
43
+ li {
44
+ display: inline-block;
45
+ @extend %nav-button;
46
+ > a {
47
+ border-color: #eee;
48
+ }
49
+ }
50
+ }
51
+ }
@@ -2,7 +2,7 @@
2
2
  @include display(flex);
3
3
  @include flex(1 1 100%);
4
4
  @include flex-flow(row nowrap);
5
-
5
+
6
6
  &.live {
7
7
  bottom: 0;
8
8
  left: 10px;
data/src/styles/app.scss CHANGED
@@ -28,6 +28,7 @@
28
28
  @import "preview";
29
29
  @import "workspace";
30
30
  @import "modal";
31
+ @import "merge_conflicts";
31
32
 
32
33
  .markuapad {
33
34
  @include display(flex);
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markuapad
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Braden Simpson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-08-10 00:00:00.000000000 Z
11
+ date: 2015-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -85,6 +85,7 @@ files:
85
85
  - src/jsx/image_modal.jsx
86
86
  - src/jsx/live_preview.jsx
87
87
  - src/jsx/main.jsx
88
+ - src/jsx/merge_conflict_modal.jsx
88
89
  - src/jsx/preview.jsx
89
90
  - src/jsx/toolbar.jsx
90
91
  - src/markuapad.js
@@ -93,6 +94,7 @@ files:
93
94
  - src/styles/_file_browser.scss
94
95
  - src/styles/_grid-settings.scss
95
96
  - src/styles/_layout.scss
97
+ - src/styles/_merge_conflicts.scss
96
98
  - src/styles/_modal.scss
97
99
  - src/styles/_preview.scss
98
100
  - src/styles/_toolbar.scss