written 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: baeba731f6551380429d586b4cf9ce6958e87cc9
4
- data.tar.gz: e8cb3235cc64c398e64cf7d87d3deee94f5eea47
3
+ metadata.gz: 3feabb75dd783639d084dd9b58cb552d195a8a38
4
+ data.tar.gz: cf8fbadf5551a7d453904460bc4ba4cb5b7f0676
5
5
  SHA512:
6
- metadata.gz: 904f533a3ab8b865659b82f7e26a10028f98708150facb7b7f9b8c0665aeabf38b8427329d6719c1bcc43ded3653c3b19db76594f99f37c99b02997bef00dc8d
7
- data.tar.gz: 3e4db620d0eac31e9cb4252104fffbcede4660aadcd3f8bb5ad784bb17505e36c944f9579a356cbe2ba7f7efe4c63f00f22f596806e85a9f70ba2da3b252d3ff
6
+ metadata.gz: 43c5f5f0d01986530cdbbba47fcfecdafc6c07b367027b3413603aba5cb33fda21adfb34dff6f1263488eb9a6f4ca50d38ac1194a3eabfe5cef39556535c8370
7
+ data.tar.gz: 10722bca8267b18cf3a268aefe0ec7bd82afb4445918b8f65c801ddf5484e0c8e212c1f7b0222351032b63fd643df4ac57c9b821e6488ac0b732d8916de65ea2
@@ -0,0 +1,113 @@
1
+ class Image extends Written.Attachments.Base
2
+ constructor: (@node) ->
3
+ @input = document.createElement('input')
4
+ @input.type = 'file'
5
+ @input.addEventListener 'change', @confirm.bind(this, @node)
6
+ @node.querySelector('img').addEventListener 'click', @select.bind(this, @node)
7
+
8
+ template: ->
9
+ "<div id='WrittenOverlay' contenteditable=false>
10
+ <div id='WrittenDialog'>
11
+ <header>
12
+ <div class='progress'></div>
13
+ <button data-action='cancel'>Cancel</button>
14
+ <h3>Use this image?</h3>
15
+ <button data-action='upload'>Use</button>
16
+ </header>
17
+ <figure>
18
+ <img />
19
+ </figure>
20
+ </div>
21
+ </div>
22
+ ".toHTML()
23
+
24
+ select: (node) =>
25
+ @selection = @getSelection()
26
+ @input.click()
27
+
28
+ confirm: (node, event) =>
29
+ @pause()
30
+ @overlay = @template()
31
+ @editor().element().appendChild(@overlay)
32
+
33
+ @image(event, @preview.bind(this, @overlay.querySelector('img')))
34
+ @overlay.querySelector('button[data-action=cancel]').addEventListener('click', @cancel)
35
+
36
+ preview: (node, event) ->
37
+ node.src = event.target.result
38
+ @overlay.querySelector('button[data-action=upload]').addEventListener('click', @upload)
39
+
40
+ image: (event, callback) ->
41
+ @file = event.target.files[0]
42
+
43
+ reader = new FileReader()
44
+ reader.addEventListener('load', callback)
45
+ reader.addEventListener('error', @failed)
46
+ reader.readAsDataURL(@file)
47
+
48
+ cancel: =>
49
+ @overlay.remove()
50
+ @resume()
51
+
52
+ complete: (name) ->
53
+ figcaption = @node.querySelector('figcaption')
54
+ url = figcaption.childNodes[figcaption.childNodes.length - 1]
55
+
56
+ @overlay.addEventListener 'transitionend', =>
57
+ @overlay.remove()
58
+ @resume =>
59
+ figcaption.replaceChild(document.createTextNode("(#{window.AWS.bucket}/#{name})"), url)
60
+
61
+ @overlay.classList.add('fade')
62
+
63
+ failed: (e) =>
64
+ dialog = @overlay.querySelector('#WrittenDialog')
65
+ dialog.classList.add 'failed'
66
+ dialog.querySelector('h3').textContent = "Failed"
67
+ dialog.querySelector('figure').remove()
68
+ dialog.appendChild("
69
+ <section>
70
+ <p>An error occured while trying to process your image.</p>
71
+ <button>Close</button>
72
+ </section>
73
+ ".toHTML())
74
+ dialog.querySelector('button').addEventListener('click', @cancel)
75
+
76
+ upload: (e) =>
77
+ @overlay.querySelector('h3').textContent = "Uploading"
78
+ Array.prototype.slice.call(@overlay.querySelectorAll('button')).forEach (button) ->
79
+ button.remove()
80
+
81
+ name = @name(@file.name)
82
+
83
+ form = new FormData()
84
+ form.append('key', name)
85
+ form.append('acl', 'private')
86
+ form.append("AWSAccessKeyId", window.AWS.accessKey)
87
+ form.append('policy', window.AWS.policy)
88
+ form.append('signature', window.AWS.signature)
89
+ form.append('Content-Type', @file.type)
90
+ form.append('file', @file, name)
91
+
92
+ xhr = new XMLHttpRequest()
93
+ xhr.addEventListener('load', @complete.bind(this, name))
94
+ xhr.addEventListener('error', @failed)
95
+ xhr.upload.addEventListener('progress', @progress)
96
+ xhr.open('POST', window.AWS.bucket)
97
+ xhr.send(form)
98
+ xhr
99
+
100
+
101
+ progress: (e) =>
102
+ value = e.loaded / e.total * 100
103
+ @overlay.querySelector('div.progress').style.width = "#{value}%"
104
+
105
+ name: (filename) ->
106
+ hash = 0
107
+ for i in [0..filename.length - 1]
108
+ hash += filename.charCodeAt(i) ^ hash
109
+
110
+ hash.toString('16')
111
+
112
+
113
+ Written.Attachments.bind 'figure', Image
@@ -0,0 +1,37 @@
1
+ @Written.Attachments = new class Attachments
2
+ constructor: ->
3
+ @attachments = {}
4
+ @nodes = []
5
+
6
+ bind: (nodeName, attachment) ->
7
+ @attachments[nodeName] = attachment
8
+
9
+ attach: (editor) =>
10
+ for name, attachment of @attachments
11
+ nodes = editor.element().querySelectorAll(name)
12
+ for node in nodes
13
+ if @nodes.includes(node)
14
+ continue
15
+
16
+ attachment = new attachment(node)
17
+ attachment.editor(editor)
18
+ @nodes.push(node)
19
+
20
+
21
+ @Written.Attachments.Base = class Attachment
22
+ editor: (editor) ->
23
+ @editor = ->
24
+ editor
25
+
26
+ @editor()
27
+
28
+ pause: =>
29
+ @editor().observer.pause()
30
+
31
+ resume: (callback) =>
32
+ @editor().observer.resume()
33
+ if callback?
34
+ callback()
35
+
36
+ getSelection: ->
37
+ window.getSelection()
@@ -38,7 +38,8 @@ class @Written
38
38
  document.cursor = cursor
39
39
 
40
40
 
41
- document.applyTo(@element())
41
+ @render(document)
42
+
42
43
  document.cursor.focus(document.toString().length)
43
44
 
44
45
  @history = new Written.History(document)
@@ -54,7 +55,7 @@ class @Written
54
55
  changeTo: (text) =>
55
56
  document = new Written.Document(text, @parsers)
56
57
  @history.push(document)
57
- document.applyTo(@element())
58
+ @render(document)
58
59
 
59
60
  changed: (e) =>
60
61
  oldDocument = @history.current
@@ -63,33 +64,14 @@ class @Written
63
64
  if @element().children.length > 0 && oldDocument.toString().localeCompare(newDocument.toString()) == 0
64
65
  return
65
66
 
66
- newDocument.applyTo(@element())
67
67
  @history.push(newDocument)
68
+ @render(newDocument)
69
+
68
70
  @dispatch('written:changed', document: newDocument)
69
71
 
70
72
  cursor: =>
71
73
  @history.current.cursor = new Written.Cursor(@element(), window.getSelection())
72
74
 
73
- update: (document) =>
74
- elements = Array.prototype.slice.call(@element().children)
75
-
76
- for block, index in document.blocks
77
- node = block.markdown()
78
- element = elements[index]
79
-
80
- if element?
81
- if !block.identical(element, node)
82
- @element().replaceChild(node, element)
83
- else
84
- @element().appendChild(node)
85
-
86
- if elements.length > index
87
- for i in [index..elements.length - 1]
88
- elements[i].remove()
89
-
90
- document.cursor.focus()
91
-
92
-
93
75
  linefeed: (e) =>
94
76
  return unless e.which == 13
95
77
  e.preventDefault()
@@ -117,10 +99,14 @@ class @Written
117
99
  if cursor.offset < document.toString().length
118
100
  cursor.offset += 1
119
101
 
120
- document.applyTo(@element())
102
+ @render(document)
121
103
  @history.push(document)
122
104
 
123
105
 
106
+ render: (document) =>
107
+ document.applyTo(@element())
108
+ Written.Attachments.attach(this)
109
+
124
110
  undo: (e) =>
125
111
  if e.code == 'KeyZ' && e.metaKey && !e.shiftKey
126
112
  e.preventDefault()
@@ -130,7 +116,7 @@ class @Written
130
116
 
131
117
  if document = @history.previous()
132
118
  @history.current = document
133
- document.applyTo(@element())
119
+ @render(document)
134
120
 
135
121
  redo: (e) =>
136
122
  if e.code == 'KeyZ' && e.metaKey && e.shiftKey
@@ -141,7 +127,7 @@ class @Written
141
127
 
142
128
  if document = @history.next()
143
129
  @history.current = document
144
- @history.current.applyTo(@element())
130
+ @render(@history.current)
145
131
 
146
132
  toString: =>
147
133
  texts = []
@@ -18,7 +18,7 @@ class Image
18
18
  rendered.querySelector('img').src == img.src
19
19
 
20
20
  markdown: =>
21
- figure = "<figure><div contenteditable='false'><input type='file' /><img/></div><figcaption /></figure>".toHTML()
21
+ figure = "<figure><div contenteditable='false'><img/></div><figcaption /></figure>".toHTML()
22
22
  caption = figure.querySelector('figcaption')
23
23
  container = figure.querySelector('div')
24
24
 
@@ -1,3 +1,3 @@
1
+ #= require ./written/core/content
1
2
  #= require_tree ./written/core
2
3
  #= require_tree ./written/parsers
3
- #= require_tree ./written/uploaders
@@ -1,4 +1,5 @@
1
1
  [data-editor="written"] {
2
+ position: relative;
2
3
  white-space: pre-wrap;
3
4
  padding: 1em;
4
5
 
@@ -29,23 +30,9 @@
29
30
  [data-editor="written"] figure > div {
30
31
  position: relative;
31
32
  display: flex;
33
+ align-items: center;
34
+ justify-content: center;
32
35
  padding: 4px;
33
-
34
- &[data-uploadable] {
35
- cursor: pointer;
36
- }
37
- }
38
-
39
- [data-editor="written"] figure > div > div.progress {
40
- position: absolute;
41
- top: 0;
42
- left: 0;
43
- transition: width 0.24s ease;
44
- }
45
-
46
- [data-editor="written"] figure[data-status='uploading'] div.progress {
47
- background-color: green;
48
- height: 1px;
49
36
  }
50
37
 
51
38
  [data-editor="written"] figure img {
@@ -99,3 +86,116 @@
99
86
  display: block;
100
87
  white-space: pre-line;
101
88
  }
89
+
90
+ [data-editor="written"] > #WrittenOverlay {
91
+ position: absolute;
92
+ top: 0;
93
+ left: 0;
94
+ right: 0;
95
+ bottom: 0;
96
+
97
+ display: flex;
98
+ align-items: center;
99
+ justify-content: center;
100
+
101
+ background-color: rgba(230, 230, 230, 0.8);
102
+
103
+ transition: opacity 0.2s ease-out;
104
+
105
+ &.fade {
106
+ opacity: 0;
107
+ }
108
+ }
109
+
110
+ [data-editor="written"] > #WrittenOverlay > #WrittenDialog {
111
+ display: flex;
112
+ flex-direction: column;
113
+
114
+ min-width: 300px;
115
+ width: 100vw;
116
+ max-width: 700px;
117
+
118
+ max-height: 800px;
119
+
120
+ background: white;
121
+
122
+ box-shadow: 0 0 3px rgba(150, 150, 150, 0.5);
123
+
124
+ overflow: hidden;
125
+
126
+ button {
127
+ outline: none;
128
+ }
129
+ }
130
+
131
+ [data-editor="written"] > #WrittenOverlay > #WrittenDialog figure {
132
+ img {
133
+ border: none;
134
+ }
135
+ }
136
+
137
+ [data-editor="written"] > #WrittenOverlay > #WrittenDialog div.progress {
138
+ position: absolute;
139
+ top: 0;
140
+ left: 0;
141
+ bottom: 0;
142
+
143
+ transition: width 0.2s ease;
144
+ background-color: #2B99E8;
145
+ box-shadow: 0 0 2px rgba(0, 147, 255, 0.41);
146
+ }
147
+
148
+ [data-editor="written"] > #WrittenOverlay > #WrittenDialog header {
149
+ position: relative;
150
+ display: flex;
151
+ justify-content: space-between;
152
+ border-bottom: 1px solid rgba(200, 200, 200, 200);
153
+ box-shadow: 0 2px whitesmoke;
154
+ background-color: whitesmoke;
155
+
156
+ h3 {
157
+ flex: 1;
158
+ margin: 0;
159
+ text-align: center;
160
+ z-index: 1;
161
+ line-height: 2em;
162
+ }
163
+
164
+ button {
165
+ border: none;
166
+ text-transform: uppercase;
167
+ color: white;
168
+ width: 100px;
169
+
170
+ &[data-action=cancel] {
171
+ background-color: red;
172
+ }
173
+
174
+ &[data-action=upload] {
175
+ background-color: green;
176
+ }
177
+ }
178
+
179
+ }
180
+
181
+ [data-editor="written"] > #WrittenOverlay > #WrittenDialog.failed header {
182
+ background-color: #CA1414;
183
+ color: white;
184
+ }
185
+
186
+ [data-editor="written"] > #WrittenOverlay > #WrittenDialog.failed section {
187
+ display: flex;
188
+ flex-direction: column;
189
+ padding: 12px;
190
+ text-align: center;
191
+ }
192
+
193
+ [data-editor="written"] > #WrittenOverlay > #WrittenDialog.failed button {
194
+ background-color: #CA1414;
195
+ text-align: center;
196
+ color: white;
197
+ width: 80%;
198
+ margin: 12px auto;
199
+ padding: 4px;
200
+ border: none;
201
+ }
@@ -1,3 +1,3 @@
1
1
  module Written
2
- VERSION = '0.1.3'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -1,6 +1,8 @@
1
1
  #= require 'written'
2
+ #= require 'written/attachments/image'
2
3
  #= require 'prism'
3
- #
4
+ #= require 'secret'
5
+
4
6
  document.removeEventListener('DOMContentLoaded', Prism.highlightAll)
5
7
 
6
8
  Written.Parsers.get('pre').highlightWith (element) ->
@@ -3,12 +3,12 @@
3
3
  1. this is a list
4
4
  2. this is another list
5
5
  3. this is another list
6
- - this is **a** list
7
- 3. this is [a](http://google.com) *list*. It's special, isn't **it**?
8
- test *boo* and them *some* or **some!!** a
6
+ - this is *a* list
7
+ 3. this is [a](http://google.com) _list_. It's special, isn't *it*?
8
+ test *boo* and them *some* or *some!!* a
9
9
 
10
10
 
11
- ![an *image* is worth a **thousand** words](image)
11
+ ![an _image_ is worth a *thousand* words](image)
12
12
 
13
13
  ~~~javascript
14
14
  var test = "hello"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: written
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pier-Olivier Thibault
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-18 00:00:00.000000000 Z
11
+ date: 2016-05-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Written is a rich Markdown editor for the web.
14
14
  email: pothibo@gmail.com
@@ -25,6 +25,8 @@ files:
25
25
  - lib/written.rb
26
26
  - lib/written/app/assets/images/written/placeholder.png
27
27
  - lib/written/app/assets/javascripts/written.coffee
28
+ - lib/written/app/assets/javascripts/written/attachments/image.coffee
29
+ - lib/written/app/assets/javascripts/written/core/attachments.coffee
28
30
  - lib/written/app/assets/javascripts/written/core/content.coffee
29
31
  - lib/written/app/assets/javascripts/written/core/cursor.coffee
30
32
  - lib/written/app/assets/javascripts/written/core/document.coffee
@@ -43,7 +45,6 @@ files:
43
45
  - lib/written/app/assets/javascripts/written/parsers/inline/link.coffee
44
46
  - lib/written/app/assets/javascripts/written/parsers/inline/strong.coffee
45
47
  - lib/written/app/assets/javascripts/written/parsers/parsers.coffee
46
- - lib/written/app/assets/javascripts/written/uploaders/aws.coffee
47
48
  - lib/written/app/assets/stylesheets/written.scss
48
49
  - lib/written/railtie.rb
49
50
  - lib/written/version.rb
@@ -1,125 +0,0 @@
1
- class Written.Uploaders.AWS
2
- constructor: (settings) ->
3
- Images.settings = settings
4
-
5
- initialize: (node, observer) =>
6
- img = node.querySelector('img')
7
- container = node.querySelector('div')
8
-
9
- node.dataset.uploadable = true
10
- node.addEventListener 'written:uploader:error', @error, true
11
- node.addEventListener 'written:uploader:uploading', @progress, true
12
- node.addEventListener 'written:uploader:completed', @uploaded, true
13
- node.addEventListener 'change', @input.bind(this, node, observer), true
14
-
15
- container.addEventListener 'click', @open.bind(this, node), true
16
- image = Images.get(img.dataset.image)
17
- if image
18
- image.node = node
19
-
20
- open: (node, e) =>
21
- node.querySelector('input')?.click()
22
-
23
- uploaded: (e) =>
24
- caption = e.target.querySelector('figcaption')
25
- img = e.target.querySelector('img')
26
-
27
- text = caption.childNodes[caption.childNodes.length - 1]
28
- text.textContent = "(#{e.image.url})"
29
- img.src = e.image.url
30
- selection = window.getSelection()
31
- selection.removeAllRanges()
32
- range = document.createRange()
33
- range.setEnd(text, text.textContent.length - 1)
34
- selection.addRange(range)
35
-
36
- error: (e) =>
37
- console.log(e)
38
-
39
- input: (node, history, e) =>
40
- file = e.target.files[0]
41
- @process(file, node, history)
42
-
43
- process: (file, node) =>
44
- caption = node.querySelector('figcaption')
45
- progress = node.querySelector('div.progress')
46
- filename = [Images.settings.namespace, @hash(file.name)].join('/')
47
-
48
- image = Images.get(filename)
49
- if !image
50
- image = Images.upload(filename, file)
51
-
52
- image.node = node
53
- progress.style.width = image.progress
54
- node.dataset.status = image.status
55
-
56
- text = caption.childNodes[caption.childNodes.length - 1]
57
- text.textContent = "(#{filename})"
58
- selection = window.getSelection()
59
- selection.removeAllRanges()
60
- range = document.createRange()
61
- range.setEnd(text, text.textContent.length - 1)
62
- selection.addRange(range)
63
-
64
- hash: (filename) ->
65
- hash = 0
66
- for i in [0..filename.length - 1]
67
- hash += filename.charCodeAt(i) ^ hash
68
-
69
- hash.toString('16')
70
-
71
- Images = new class
72
- constructor: ->
73
- @cache = {}
74
-
75
- failed: (image, event) ->
76
- image.status = 'failed'
77
-
78
- if image.node?
79
- event = new CustomEvent('written:uploader:error', {bubbles: true})
80
- event.image = image
81
- image.node.dispatchEvent(event)
82
-
83
- update: (image, event) ->
84
- image.status = 'completed'
85
-
86
- if image.node?
87
- event = new CustomEvent('written:uploader:completed', {bubbles: true})
88
- event.image = image
89
- image.node.dispatchEvent(event)
90
-
91
- upload: (name, file) =>
92
- image = {
93
- url: "#{@settings.url}/#{name}"
94
- name: name
95
- status: 'uploading'
96
- progress: 0
97
- }
98
-
99
- form = new FormData()
100
- form.append('key', name)
101
- form.append('acl', 'private')
102
- form.append("AWSAccessKeyId", @settings.accessKey)
103
- form.append('policy', @settings.policy)
104
- form.append('signature', @settings.signature)
105
- form.append('Content-Type', file.type)
106
- form.append('file', file, name)
107
-
108
-
109
- image.xhr = @send(form, @settings.url)
110
- image.xhr.addEventListener('load', @update.bind(this, image))
111
- image.xhr.addEventListener('error', @failed.bind(this, image))
112
-
113
- @cache[image.name] = image
114
-
115
-
116
- get: (name) ->
117
- @cache[name]
118
-
119
- send: (form, url) =>
120
- xhr = new XMLHttpRequest()
121
- xhr.open('POST', url)
122
- xhr.send(form)
123
- xhr
124
-
125
-