written 0.1.3 → 0.2.0

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.
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
-