markuapad 0.1.7

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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.babelrc +3 -0
  3. data/.gitignore +30 -0
  4. data/.jshintrc +4 -0
  5. data/CODE_OF_CONDUCT.md +13 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +21 -0
  8. data/README.md +30 -0
  9. data/Rakefile +1 -0
  10. data/app/assets/javascripts/markuapad.js +72 -0
  11. data/app/assets/stylesheets/markuapad.css +1 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +7 -0
  14. data/bower.json +30 -0
  15. data/build/app.css +1 -0
  16. data/build/app.js +72 -0
  17. data/build/browser-polyfill.min.js +3 -0
  18. data/build/dist.css +1 -0
  19. data/build/dist.js +72 -0
  20. data/build/index.css +1 -0
  21. data/build/index.html +30 -0
  22. data/build/index.js +1 -0
  23. data/build/web.css +1 -0
  24. data/build/web.js +72 -0
  25. data/lib/markuapad.rb +5 -0
  26. data/lib/markuapad/engine.rb +4 -0
  27. data/lib/markuapad/version.rb +3 -0
  28. data/markuapad.gemspec +32 -0
  29. data/package.json +55 -0
  30. data/prepare_gem.sh +3 -0
  31. data/src/dist.js +7 -0
  32. data/src/file_accessor.js +77 -0
  33. data/src/index.js +180 -0
  34. data/src/jsx/editor.jsx +122 -0
  35. data/src/jsx/file_browser.jsx +252 -0
  36. data/src/jsx/file_browser_list_item.jsx +86 -0
  37. data/src/jsx/image_modal.jsx +49 -0
  38. data/src/jsx/live_preview.jsx +36 -0
  39. data/src/jsx/main.jsx +145 -0
  40. data/src/jsx/preview.jsx +32 -0
  41. data/src/jsx/toolbar.jsx +24 -0
  42. data/src/markuapad.js +39 -0
  43. data/src/styles/_editor.scss +27 -0
  44. data/src/styles/_extendables.scss +115 -0
  45. data/src/styles/_file_browser.scss +160 -0
  46. data/src/styles/_grid-settings.scss +13 -0
  47. data/src/styles/_layout.scss +18 -0
  48. data/src/styles/_modal.scss +145 -0
  49. data/src/styles/_preview.scss +65 -0
  50. data/src/styles/_toolbar.scss +99 -0
  51. data/src/styles/_variables.scss +25 -0
  52. data/src/styles/_workspace.scss +31 -0
  53. data/src/styles/app.scss +52 -0
  54. data/src/styles/index.scss +41 -0
  55. data/src/util.js +43 -0
  56. data/src/web.js +4 -0
  57. data/test/editor.es6 +6 -0
  58. data/webpack.config.js +48 -0
  59. metadata +129 -0
@@ -0,0 +1,49 @@
1
+ import React from "react";
2
+ import FileAccessor from "../file_accessor"
3
+
4
+ class ImageModal extends React.Component {
5
+
6
+ componentDidMount() {
7
+ this.grabImageData();
8
+ }
9
+
10
+ componentWillReceiveProps(nextProps) {
11
+ if (nextProps.file !== this.props.file) this.grabImageData();
12
+ }
13
+
14
+ grabImageData() {
15
+ FileAccessor.get(this.props.file.filename, this.forceUpdate.bind(this), "image");
16
+ }
17
+
18
+ getSelectedImageDataURI() {
19
+ let imageFile = FileAccessor.getSync(this.props.file.filename, "image")
20
+
21
+ if (!imageFile || !imageFile.data)
22
+ return null
23
+
24
+ return `data:${imageFile.mimetype.string};base64,${imageFile.data}`
25
+ }
26
+
27
+ render() {
28
+ return (
29
+ <div className="modal">
30
+ <div className="modal-fade-screen">
31
+ <div className="modal-inner">
32
+ <div className="modal-close" onClick={ this.props.onClose }></div>
33
+ <h1>{ this.props.file.filename }</h1>
34
+ <div className="modal-content">
35
+ <img src={ this.getSelectedImageDataURI() } />
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ );
41
+ }
42
+ }
43
+
44
+ ImageModal.propTypes = {
45
+ file: React.PropTypes.object.isRequired,
46
+ onClose: React.PropTypes.func.isRequired
47
+ }
48
+
49
+ export default ImageModal;
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+ import { fixImagePaths } from "../util"
3
+ import { default as $ } from "jquery"
4
+
5
+ class LivePreview extends React.Component {
6
+ componentDidUpdate(lastProps, lastState) {
7
+ if (this.props.previewState !== lastProps.previewState) {
8
+ let cursor = $("[data-markua-cursor-position='__markuaCursorPosition__']").get(0)
9
+ if (cursor)
10
+ $(React.findDOMNode(this)).animate({
11
+ scrollTop: cursor.offsetTop - 100
12
+ }, 200);
13
+ }
14
+ }
15
+
16
+ render() {
17
+ return (
18
+ <section className={`live-preview ${this.props.previewState}`}>
19
+ <section className="preloader"></section>
20
+ <section className="previewWarnigs"></section>
21
+ <section className="previewErrors">
22
+ { this.props.previewErrors }
23
+ </section>
24
+ <div ref="content" dangerouslySetInnerHTML={{ __html: fixImagePaths(this.props.html, this.forceUpdate.bind(this)) }} className="container content" />
25
+ </section>
26
+ );
27
+ }
28
+ }
29
+
30
+ LivePreview.propTypes = {
31
+ html: React.PropTypes.string.isRequired,
32
+ previewState: React.PropTypes.string.isRequired,
33
+ previewErrors: React.PropTypes.string
34
+ }
35
+
36
+ export default LivePreview;
data/src/jsx/main.jsx ADDED
@@ -0,0 +1,145 @@
1
+ import React from "react";
2
+
3
+ import Editor from "./editor";
4
+ import Toolbar from "./toolbar";
5
+ import FileBrowser from "./file_browser";
6
+ import Preview from "./preview";
7
+ import LivePreview from "./live_preview";
8
+ import FileAccessor from "../file_accessor";
9
+ import ImageModal from "./image_modal";
10
+ import _ from "underscore";
11
+
12
+ import { getCached } from "../util"
13
+
14
+ class Main extends React.Component {
15
+ constructor(props) {
16
+ super(props);
17
+
18
+ // Setup some initial state
19
+ this.state = {
20
+ currentFile: null,
21
+ imageFile: null,
22
+ previewState: 'closed',
23
+ inLiveMode: true,
24
+ previewHtml: ""
25
+ }
26
+
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
+ // File access hooks
40
+ FileAccessor.onDelete(this.onManuscriptChange);
41
+ FileAccessor.onManuscriptChange(this.onManuscriptChange);
42
+ FileAccessor.onAdd(this.onFileAdded);
43
+ }
44
+
45
+ // Lifecycle methods
46
+ componentDidMount() {
47
+ // Trigger a preview right away -- since we start in live mode
48
+ this.onGeneratePreview();
49
+ }
50
+
51
+ // File event operations
52
+ onFileAdded(file) {
53
+ this.setState({ currentFile: file });
54
+ }
55
+
56
+ onManuscriptChange() {
57
+ // Re-preview
58
+ if (this.state.inLiveMode)
59
+ this.onGeneratePreview();
60
+ }
61
+
62
+ onPreviewImage(file) {
63
+ this.setState({ imageFile: file });
64
+ }
65
+
66
+ onChangeFile(file) {
67
+ this.setState({ currentFile: file });
68
+ }
69
+
70
+ // Preview based methods
71
+ onPreviewReady(errors, html) {
72
+ // If someone has already stopped the preview then just bail
73
+ if (this.state.previewState === "closed")
74
+ return
75
+
76
+ this.setState({ previewHtml: html, previewState: "done", previewErrors: errors })
77
+ }
78
+
79
+ onGeneratePreview(e) {
80
+ // Call out to the markua processor
81
+ this.setState({ previewState: "previewing" });
82
+ this.props.markua.run(this.onPreviewReady, { cursor: getCached("markuapad_cursor") })
83
+ }
84
+
85
+ onClosePreview(e) {
86
+ this.setState({ previewState: "closed" })
87
+ }
88
+
89
+ toggleLiveMode() {
90
+ // Clear the preview state here as well, so we don't accidentally open a preview
91
+ this.setState({ inLiveMode: !this.state.inLiveMode, previewState: "closed" }, () => {
92
+ window.dispatchEvent(new Event('resize'));
93
+
94
+ // If we transition into live mode, then kick off an initial preview;
95
+ if (this.state.inLiveMode)
96
+ this.onGeneratePreview();
97
+ });
98
+ }
99
+
100
+ onBookContentChanged() {
101
+ if (this.state.inLiveMode)
102
+ this.onGeneratePreview();
103
+ }
104
+
105
+ getWorkspaceClass() {
106
+ let workspaceClass = `workspace`;
107
+ workspaceClass += this.state.inLiveMode ? ' live' : '';
108
+ return workspaceClass;
109
+ }
110
+
111
+ render() {
112
+ return (
113
+ <section className="markuapad">
114
+ <Toolbar
115
+ bookTitle={this.props.bookTitle}
116
+ onGeneratePreview={this.onGeneratePreview}
117
+ toggleLiveMode={this.toggleLiveMode}
118
+ inLiveMode={this.state.inLiveMode}
119
+ />
120
+ <section className="main-view">
121
+ <section className={this.getWorkspaceClass()}>
122
+ <FileBrowser onPreviewImage={this.onPreviewImage} onChangeFile={this.onChangeFile} currentFile={this.state.currentFile} projectRoot={this.props.projectRoot }/>
123
+ <Editor
124
+ onBookContentChanged={this.onBookContentChanged}
125
+ inLiveMode={this.state.inLiveMode}
126
+ currentFile={this.state.currentFile}
127
+ />
128
+ { this.state.inLiveMode ? <LivePreview key='live-mode' ref="liveMode" html={this.state.previewHtml} previewState={this.state.previewState} previewErrors={this.state.previewErrors} /> : null }
129
+ </section>
130
+ </section>
131
+ { this.state.inLiveMode ? <span /> : <Preview key='preview' onClosePreview={this.onClosePreview} html={this.state.previewHtml} previewState={this.state.previewState} previewErrors={this.state.previewErrors} /> }
132
+ { this.state.imageFile ? <ImageModal file={this.state.imageFile} onClose={ _.partial(this.onPreviewImage, null) } /> : null }
133
+ </section>
134
+ );
135
+ }
136
+ }
137
+
138
+ Main.propTypes = {
139
+ markua: React.PropTypes.object.isRequired,
140
+ bookTitle: React.PropTypes.string.isRequired,
141
+ projectRoot: React.PropTypes.string.isRequired,
142
+ options: React.PropTypes.object.isRequired
143
+ };
144
+
145
+ export default Main;
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import { fixImagePaths } from "../util"
3
+
4
+ class Preview extends React.Component {
5
+ render() {
6
+ let clazz = `preview ${this.props.previewState}`
7
+
8
+ return (
9
+ <section className={clazz}>
10
+ <header className="container">
11
+ <button className="close" onClick={this.props.onClosePreview}><i className="fa fa-times fa-2x"></i></button>
12
+ </header>
13
+ <section className="preloader"></section>
14
+ <section className="previewWarnigs"></section>
15
+ <section className="previewErrors">
16
+ { this.props.previewErrors }
17
+ </section>
18
+ <div dangerouslySetInnerHTML={{ __html: fixImagePaths(this.props.html, this.forceUpdate.bind(this)) }} className="container content" />
19
+ </section>
20
+ );
21
+ }
22
+ }
23
+
24
+ Preview.propTypes = {
25
+ inLiveMode: React.PropTypes.bool.isRequired,
26
+ onClosePreview: React.PropTypes.func.isRequired,
27
+ html: React.PropTypes.string.isRequired,
28
+ previewState: React.PropTypes.string.isRequired,
29
+ previewErrors: React.PropTypes.string
30
+ }
31
+
32
+ export default Preview;
@@ -0,0 +1,24 @@
1
+ import React from "react";
2
+
3
+ class Toolbar extends React.Component {
4
+ render() {
5
+ return (
6
+ <nav className="toolbar">
7
+ <h3 className="book-title">{ this.props.bookTitle}</h3>
8
+ <ul className="actions">
9
+ <li><a className={this.props.inLiveMode ? 'active' : '' } onClick={this.props.toggleLiveMode}><i className="fa fa-columns"></i> Live Mode</a></li>
10
+ <li><a className={this.props.inLiveMode ? 'disabled' : ''} onClick={this.props.onGeneratePreview}><i className="fa fa-play"></i> Preview</a></li>
11
+ </ul>
12
+ </nav>
13
+ );
14
+ }
15
+ }
16
+
17
+ Toolbar.propTypes = {
18
+ inLiveMode: React.PropTypes.bool.isRequired,
19
+ toggleLiveMode: React.PropTypes.func.isRequired,
20
+ onGeneratePreview: React.PropTypes.func.isRequired,
21
+ bookTitle: React.PropTypes.string.isRequired
22
+ }
23
+
24
+ export default Toolbar;
data/src/markuapad.js ADDED
@@ -0,0 +1,39 @@
1
+ // Get the stylesheet
2
+ require("./styles/app.scss");
3
+
4
+ import _ from "underscore";
5
+ import _string from "underscore.string";
6
+ import Main from "./jsx/main";
7
+ import React from "react";
8
+ import Markua from "markua-js";
9
+ import FileAccessor from "./file_accessor";
10
+
11
+ _.string = _string;
12
+
13
+ let DEFAULT_OPTIONS = {}
14
+
15
+ class Markuapad {
16
+ constructor() {
17
+ this.options = DEFAULT_OPTIONS;
18
+ }
19
+
20
+ create(elementId, options = {}) {
21
+ // apply options to defaults
22
+ this.options = _.extend(this.options, options);
23
+
24
+ // Project Root
25
+ let projectTitle = options.title || "My First Markuapad Book";
26
+ let projectRoot = options.slug || _.string.slugify(projectTitle);
27
+
28
+ // Instantiate a new markua processor instance
29
+ this.markua = new Markua(projectRoot, { fileAccessor: this.options.fileAccessor })
30
+
31
+ // Setup the markuapad file accessor
32
+ FileAccessor.setup(this.options.fileAccessor, projectRoot);
33
+
34
+ // Render the markuapad
35
+ React.render(React.createFactory(Main)({ bookTitle: projectTitle, options: options, markua: this.markua, projectRoot: projectRoot }), document.getElementById(elementId));
36
+ }
37
+ }
38
+
39
+ export default new Markuapad();
@@ -0,0 +1,27 @@
1
+ .markuapad {
2
+ .editor {
3
+ @extend %box-shadow;
4
+ @include flex(1);
5
+
6
+ // Advanced font feature settings break the ace editor
7
+ font-feature-settings: initial;
8
+ -webkit-font-feature-settings: initial;
9
+ -ms-font-feature-settings: initial;
10
+ -moz-font-feature-settings: initial;
11
+
12
+ // No file selected styles
13
+ &.nofile {
14
+ text-align: center;
15
+ padding: 15% 0;
16
+ background: $editor-background;
17
+ }
18
+
19
+ // Overrides from ace editor
20
+ &.emacs-mode .ace_cursor {
21
+ background-color: #222;
22
+ border: none !important;
23
+ border-left: 1px solid #222 !important;
24
+ width: 2px !important;
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,115 @@
1
+ // TODO: move these intro individual files once they get to large.. e.g. stylesheets/common/extendables/lists.scss
2
+ // use this as the base file and include the extendables files from an extendables directory
3
+
4
+ // TODO: these shadows need some serious work.. consider using linear-gradient instead of box-shadow because we will have more control. Or at least ONLY use box shadow or ONLY use inset shadow... attching them to pseudo elements as linear gradients might be good
5
+
6
+ // Shadows
7
+ $shadow-color: rgba(0, 0, 0, 0.4);
8
+ $shadow-blur: 40px;
9
+ $shadow-spread: -$shadow-blur / 2;
10
+
11
+ // Inset
12
+ %inset-shadow-top { box-shadow: inset 0 7px $shadow-blur $shadow-spread $shadow-color; }
13
+ %inset-shadow-bottom { box-shadow: inset 0 -7px $shadow-blur $shadow-spread $shadow-color; }
14
+
15
+ %inset-shadow-top-bottom {
16
+ box-shadow: inset 0 7px $shadow-blur $shadow-spread $shadow-color,
17
+ inset 0 -7px $shadow-blur $shadow-spread $shadow-color;
18
+ }
19
+
20
+ // Box shadows
21
+ %box-shadow-top { box-shadow: 0 -7px $shadow-blur $shadow-spread $shadow-color; }
22
+ %box-shadow-bottom { box-shadow: 0 7px $shadow-blur $shadow-spread $shadow-color; }
23
+
24
+ %box-shadow-top-bottom {
25
+ box-shadow: 0 7px $shadow-blur $shadow-spread $shadow-color,
26
+ 0 -7px $shadow-blur $shadow-spread $shadow-color;
27
+ }
28
+
29
+ // Box and Inset
30
+ %box-shadow-bottom-inset-top {
31
+ box-shadow: inset 0 7px $shadow-blur $shadow-spread $shadow-color,
32
+ 0 7px $shadow-blur $shadow-spread $shadow-color;
33
+ }
34
+
35
+ %book-drop-shadow {
36
+ position: relative;
37
+ box-shadow: 0 2px 3px rgba(0, 0, 0, .33);
38
+ }
39
+
40
+ %box-shadow {
41
+ position: relative;
42
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.10);
43
+ }
44
+ %radial-drop-shadow {
45
+ position: relative;
46
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
47
+ &:after{
48
+ content: "";
49
+ position: absolute;
50
+ z-index: -1;
51
+ box-shadow: 0 0 40px rgba(0,0,0,0.8);
52
+ bottom: 0px;
53
+ left: 10%;
54
+ right: 10%;
55
+ width: 80%;
56
+ height: 50%;
57
+ border-radius: 100%;
58
+ }
59
+ }
60
+
61
+ // Image replacement mixin http://nicolasgallagher.com/another-css-image-replacement-technique/
62
+ %replace-text {
63
+ border: 0;
64
+ font: 0/0 a;
65
+ text-shadow: none;
66
+ color: transparent;
67
+ }
68
+
69
+ // Layout
70
+ %absolute-center {
71
+ position: absolute;
72
+ top: 0;
73
+ right: 0;
74
+ bottom: 0;
75
+ left: 0;
76
+ margin: auto;
77
+ }
78
+
79
+ // Lists
80
+ %horizontal-list {
81
+ text-align: center;
82
+ li {
83
+ text-align: inherit;
84
+ display: inline-block;
85
+ list-style-type: none;
86
+ margin-right: 1em;
87
+ &:last-child { margin-right: 0; }
88
+ }
89
+ }
90
+
91
+ // Transitions and animations
92
+ %base-transition {
93
+ @include transition(all, $base-transition-duration, ease-in-out);
94
+ }
95
+
96
+ %vanilla-button {
97
+ @include appearance(none);
98
+ background: initial;
99
+ border-radius: initial;
100
+ color: initial;
101
+
102
+ &:focus {
103
+ outline: none;
104
+ }
105
+ }
106
+
107
+ %close-button {
108
+ @extend %vanilla-button;
109
+
110
+ position: absolute;
111
+ right: 5px;
112
+ top: 5px;
113
+
114
+ padding: 5px;
115
+ }