written 0.0.5 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/LICENSE +8 -0
- data/README.md +63 -0
- data/Rakefile +17 -27
- data/lib/written.rb +0 -8
- data/lib/written/app/assets/images/written/placeholder.png +0 -0
- data/lib/written/app/assets/javascripts/written.coffee +2 -0
- data/lib/written/app/assets/javascripts/written/core/content.coffee +53 -35
- data/lib/written/app/assets/javascripts/written/core/cursor.coffee +33 -12
- data/lib/written/app/assets/javascripts/written/core/document.coffee +16 -11
- data/lib/written/app/assets/javascripts/written/core/extensions/string.coffee +9 -0
- data/lib/written/app/assets/javascripts/written/core/extensions/text.coffee +2 -0
- data/lib/written/app/assets/javascripts/written/core/history.coffee +2 -0
- data/lib/written/app/assets/javascripts/written/core/observer.coffee +6 -2
- data/lib/written/app/assets/javascripts/written/core/stringify.coffee +15 -0
- data/lib/written/app/assets/javascripts/written/parsers/block.coffee +69 -0
- data/lib/written/app/assets/javascripts/written/parsers/block/code.coffee +79 -15
- data/lib/written/app/assets/javascripts/written/parsers/block/heading.coffee +60 -5
- data/lib/written/app/assets/javascripts/written/parsers/block/image.coffee +103 -9
- data/lib/written/app/assets/javascripts/written/parsers/block/olist.coffee +94 -12
- data/lib/written/app/assets/javascripts/written/parsers/block/paragraph.coffee +63 -5
- data/lib/written/app/assets/javascripts/written/parsers/block/quote.coffee +92 -0
- data/lib/written/app/assets/javascripts/written/parsers/block/ulist.coffee +93 -12
- data/lib/written/app/assets/javascripts/written/parsers/inline.coffee +81 -0
- data/lib/written/app/assets/javascripts/written/parsers/inline/code.coffee +57 -0
- data/lib/written/app/assets/javascripts/written/parsers/inline/italic.coffee +40 -7
- data/lib/written/app/assets/javascripts/written/parsers/inline/link.coffee +43 -13
- data/lib/written/app/assets/javascripts/written/parsers/inline/list.coffee +27 -0
- data/lib/written/app/assets/javascripts/written/parsers/inline/strong.coffee +41 -7
- data/lib/written/app/assets/javascripts/written/parsers/parsers.coffee +21 -107
- data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/CustomElements.js +32 -0
- data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/base.js +40 -0
- data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/boot.js +124 -0
- data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/observe.js +318 -0
- data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/register.js +369 -0
- data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/traverse.js +86 -0
- data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/upgrade.js +130 -0
- data/lib/written/app/assets/javascripts/written/polyfills/MutationObserver/MutationObserver.js +575 -0
- data/lib/written/app/assets/javascripts/written/polyfills/WeakMap/WeakMap.js +49 -0
- data/lib/written/app/assets/javascripts/written/polyfills/base.coffee +10 -0
- data/lib/written/app/assets/javascripts/written/polyfills/dom.js +104 -0
- data/lib/written/app/assets/javascripts/written/uploaders/aws.coffee +125 -0
- data/lib/written/app/assets/stylesheets/written.scss +80 -11
- data/lib/written/version.rb +1 -1
- data/test/server/app/assets/javascripts/application.coffee +20 -2
- data/test/server/app/assets/stylesheets/application.scss +2 -2
- data/test/server/app/assets/stylesheets/prism.css +0 -1
- data/test/server/app/views/posts/show.html.erb +10 -3
- metadata +26 -20
- data/lib/written/app/assets/javascripts/written/core/ext.coffee +0 -109
- data/lib/written/app/assets/javascripts/written/core/extensions.coffee +0 -2
- data/lib/written/document.rb +0 -42
- data/lib/written/node.rb +0 -21
- data/lib/written/nodes/code.rb +0 -65
- data/lib/written/nodes/heading.rb +0 -15
- data/lib/written/nodes/image.rb +0 -14
- data/lib/written/nodes/ordered_list.rb +0 -18
- data/lib/written/nodes/unordered_list.rb +0 -19
- data/lib/written/parsers.rb +0 -11
- data/lib/written/parsers/base.rb +0 -26
- data/lib/written/parsers/code.rb +0 -60
- data/lib/written/parsers/heading.rb +0 -19
- data/lib/written/parsers/image.rb +0 -19
- data/lib/written/parsers/link.rb +0 -12
- data/lib/written/parsers/list.rb +0 -33
- data/lib/written/parsers/word.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ba35767946cb9b007468b0ba15fcd1d154f627b
|
4
|
+
data.tar.gz: 7bc3ccaab8687dc4a17bc52322d088a75af27fe6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f19e09c7f1fd9f2993231535a1bdf10119c4a412950cb0152992366385a4294220b674aed556708cfca13ae895744876451eb617aaf2ff3b9691647df624f468
|
7
|
+
data.tar.gz: 421434a2a0bfb187357ec99aa9c14be898921137218971015552291d283cd4e610896ecdc316e7d24cf07ec705afb09abe727cd9ec33cdcee5d7c3c7c1907393
|
data/.gitignore
CHANGED
data/LICENSE
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
Copyright (c) 2016- Pier-Olivier Thibault
|
2
|
+
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
5
|
+
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
7
|
+
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Written
|
2
|
+
|
3
|
+
Written is an easy to use, customizable WYSIWYG markdown editor. Built for evergreen browsers, it is a standalone editor with **zero dependency**. Based on the idea of [iA Writer](https://ia.net/writer/mac/), this editor uses HTML to render your text as it should look.
|
4
|
+
|
5
|
+
As you type, your text is parsed and the document take shapes. Making you look at your draft like you would on the final product. Because Markdown you *type* the style of your text, you can start your text on a different editor and copy/paste it into Written. The style will always carry over.
|
6
|
+
|
7
|
+
### Easy to style
|
8
|
+
|
9
|
+
Paragraphs are paragraphs, lists are lists and headers are... you got it, headers. This allows you to easily include Written in your project and style it very quickly to its surrounding. Ease of customization is one of the thing that drove the creation of Written.
|
10
|
+
|
11
|
+
The editor also allows you to cherry-pick the markdown feature you wish to support on your site. You can also create your own parser if you need something very specific that doesn't come out of the box.
|
12
|
+
|
13
|
+
|
14
|
+
### How to use
|
15
|
+
|
16
|
+
To start using Written, you just have to create a new editor.
|
17
|
+
|
18
|
+
~~~javascript
|
19
|
+
var editor = new Written(document.querySelector('#Editor'))
|
20
|
+
editor.initialize()
|
21
|
+
~~~
|
22
|
+
|
23
|
+
You can retrieve the document either as a markdown text, or a HTML string. For storage purposes, you might want to store the HTML string from the current document
|
24
|
+
|
25
|
+
~~~javascript
|
26
|
+
var html = document.querySelector('#Editor').instance.history.current.toHTMLString()
|
27
|
+
var markdown = document.querySelector('#Editor').instance.history.current.toString()
|
28
|
+
~~~
|
29
|
+
|
30
|
+
### Enable the parsers you need
|
31
|
+
|
32
|
+
Written allows you to enable the parsers you wish. The parsers are split into two categories: Inline and Block parsers. If you don't specify any parser during the initialization, Written, will enable all the feature by default.
|
33
|
+
|
34
|
+
The editor needs to be configured before it is initialized. Here's how you customize the parsers.
|
35
|
+
|
36
|
+
~~~javascript
|
37
|
+
var editor = new Written(document.querySelector('#Editor'))
|
38
|
+
editor.parsers.use('block', ['Header', 'Code', 'UList', 'OList'])
|
39
|
+
editor.parsers.use('inline', 'all')
|
40
|
+
editor.initialize()
|
41
|
+
~~~
|
42
|
+
|
43
|
+
You can specify ~~~ 'all'~~~ if you wish to use all the available parsers for a given type.
|
44
|
+
|
45
|
+
### Document
|
46
|
+
|
47
|
+
Based on the document system described in [Trix](https://github.com/basecamp/trix), a document is created whenever the text is modified in the editor. Document's job is to store the cursor position as well as the HTML elements rendered by the parser.
|
48
|
+
|
49
|
+
This means that Written can render text from a document and also position the cursor at the right place.
|
50
|
+
|
51
|
+
Those documents are then stored in a history that Written then use to implement the undo/redo feature.
|
52
|
+
|
53
|
+
### On Change Events
|
54
|
+
|
55
|
+
Written dispatch an event whenever text changes on the Editor. To receive update when the editor change, just add an event listener to ~~~written:changed~~~ event.
|
56
|
+
|
57
|
+
~~~javascript
|
58
|
+
document.addEventListener('written:changed', function(event) {
|
59
|
+
let document = event.detail.document
|
60
|
+
document.toHTMLString() // HTML version
|
61
|
+
document.toString() // Markdown version
|
62
|
+
})
|
63
|
+
~~~
|
data/Rakefile
CHANGED
@@ -6,33 +6,6 @@ task default: %w[test]
|
|
6
6
|
|
7
7
|
|
8
8
|
namespace :test do
|
9
|
-
desc 'Run Javascript tests'
|
10
|
-
task :javascript do
|
11
|
-
require 'rails'
|
12
|
-
require 'written'
|
13
|
-
require 'sprockets'
|
14
|
-
require 'tempfile'
|
15
|
-
environment = Sprockets::Environment.new
|
16
|
-
ThoughtJS::Railtie.instance.paths['app/assets'].existent.each do |path|
|
17
|
-
environment.append_path path
|
18
|
-
end
|
19
|
-
|
20
|
-
environment.append_path 'test/javascript'
|
21
|
-
|
22
|
-
test = Tempfile.new 'test.js'
|
23
|
-
test.write environment['test.js']
|
24
|
-
test.rewind
|
25
|
-
|
26
|
-
runner = Tempfile.new 'runner.js'
|
27
|
-
runner.write environment['runner.js']
|
28
|
-
runner.rewind
|
29
|
-
|
30
|
-
# Use system so it's possible to know if the tests were successful
|
31
|
-
# based on the return value
|
32
|
-
system("phantomjs #{runner.path} #{test.path}")
|
33
|
-
test.close
|
34
|
-
runner.close
|
35
|
-
end
|
36
9
|
|
37
10
|
Rake::TestTask.new do |t|
|
38
11
|
t.name = 'ruby'
|
@@ -81,3 +54,20 @@ task :server do
|
|
81
54
|
server.start
|
82
55
|
end
|
83
56
|
end
|
57
|
+
|
58
|
+
desc 'Compile assets for release'
|
59
|
+
task :compile do
|
60
|
+
require 'bundler/setup'
|
61
|
+
ENV['RAILS_ENV'] ||= 'development'
|
62
|
+
Bundler.require(:default, ENV['RAILS_ENV'])
|
63
|
+
|
64
|
+
environment = Sprockets::Environment.new
|
65
|
+
|
66
|
+
environment.append_path 'lib/written/app/assets/javascripts'
|
67
|
+
environment.append_path 'lib/written/app/assets/stylesheets'
|
68
|
+
|
69
|
+
manifest = Sprockets::Manifest.new(environment, 'build/manifest.json')
|
70
|
+
manifest.compile('written.*')
|
71
|
+
|
72
|
+
|
73
|
+
end
|
data/lib/written.rb
CHANGED
Binary file
|
@@ -5,72 +5,89 @@ class @Written
|
|
5
5
|
@element = ->
|
6
6
|
return el
|
7
7
|
|
8
|
+
text = @toString()
|
9
|
+
@element().textContent = ''
|
10
|
+
|
8
11
|
@observer = new Written.Observer(@element(), @changed)
|
9
|
-
@
|
12
|
+
@initialize = @initialize.bind(this, text)
|
13
|
+
|
14
|
+
@element().addEventListener 'dragover', @over
|
15
|
+
@element().addEventListener('drop', @preventDefaults)
|
10
16
|
|
11
17
|
@element().addEventListener('keypress', @linefeed)
|
12
18
|
@element().addEventListener('keydown', @undo)
|
13
19
|
@element().addEventListener('keydown', @redo)
|
20
|
+
@element().addEventListener('keydown', @cursor)
|
14
21
|
|
15
22
|
|
16
|
-
|
17
|
-
|
18
|
-
|
23
|
+
preventDefaults: (e) ->
|
24
|
+
e.preventDefault()
|
25
|
+
|
26
|
+
initialize: (text, parsers) ->
|
27
|
+
@observer.pause()
|
28
|
+
if !parsers?
|
29
|
+
parsers = Written.Parsers.default()
|
30
|
+
|
31
|
+
@parsers = parsers
|
19
32
|
|
20
|
-
|
33
|
+
if @element().contentEditable != 'true'
|
34
|
+
@element().contentEditable = 'true'
|
21
35
|
|
22
|
-
|
36
|
+
@parsers.freeze()
|
37
|
+
|
38
|
+
document = new Written.Document(text, @parsers)
|
23
39
|
cursor = new Written.Cursor(@element(), window.getSelection())
|
24
40
|
document.cursor = cursor
|
25
41
|
|
26
|
-
@history = new Written.History(document)
|
27
42
|
|
28
|
-
|
43
|
+
@update(document)
|
44
|
+
document.cursor.focus(document.toString().length)
|
29
45
|
|
30
|
-
@
|
46
|
+
@history = new Written.History(document)
|
47
|
+
|
48
|
+
@dispatch('written:initialized')
|
49
|
+
@observer.resume()
|
31
50
|
|
32
|
-
while node
|
33
|
-
@element().appendChild(node.cloneNode(true))
|
34
|
-
node = node.nextDocumentNode
|
35
51
|
|
36
|
-
|
52
|
+
dispatch: (name, data = {}) =>
|
53
|
+
event = new CustomEvent(name, bubbles: true, detail: data)
|
54
|
+
@element().dispatchEvent(event)
|
37
55
|
|
56
|
+
changeTo: (text) =>
|
57
|
+
@update(new Written.Document(text, @parsers))
|
38
58
|
|
39
59
|
changed: (e) =>
|
40
60
|
oldDocument = @history.current
|
41
|
-
newDocument = new Written.Document(@toString())
|
61
|
+
newDocument = new Written.Document(@toString(), @parsers)
|
42
62
|
newDocument.cursor = new Written.Cursor(@element(), window.getSelection())
|
43
63
|
if @element().children.length > 0 && oldDocument.toString().localeCompare(newDocument.toString()) == 0
|
44
64
|
return
|
45
65
|
|
46
66
|
@update(newDocument)
|
47
67
|
@history.push(newDocument)
|
68
|
+
@dispatch('written:changed', document: newDocument)
|
69
|
+
|
70
|
+
cursor: =>
|
71
|
+
@history.current.cursor = new Written.Cursor(@element(), window.getSelection())
|
48
72
|
|
49
73
|
update: (document) =>
|
50
|
-
|
51
|
-
cursor = document.cursor
|
52
|
-
current = @element().firstElementChild
|
74
|
+
elements = Array.prototype.slice.call(@element().children)
|
53
75
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
else if node.outerHTML.localeCompare(current.outerHTML) != 0
|
58
|
-
clonedNodeDocument = node.cloneNode(true)
|
59
|
-
@element().replaceChild(clonedNodeDocument, current)
|
60
|
-
current = clonedNodeDocument
|
76
|
+
for block, index in document.blocks
|
77
|
+
node = block.markdown()
|
78
|
+
element = elements[index]
|
61
79
|
|
62
|
-
|
63
|
-
|
64
|
-
|
80
|
+
if element?
|
81
|
+
if !block.identical(element, node)
|
82
|
+
@element().replaceChild(node, element)
|
83
|
+
else
|
84
|
+
@element().appendChild(node)
|
65
85
|
|
66
|
-
if
|
67
|
-
|
68
|
-
|
69
|
-
nextNode = node.nextElementSibling
|
70
|
-
node.remove()
|
71
|
-
node = nextNode
|
86
|
+
if elements.length > index
|
87
|
+
for i in [index..elements.length - 1]
|
88
|
+
elements[i].remove()
|
72
89
|
|
73
|
-
cursor.focus()
|
90
|
+
document.cursor.focus()
|
74
91
|
|
75
92
|
|
76
93
|
linefeed: (e) =>
|
@@ -95,7 +112,7 @@ class @Written
|
|
95
112
|
lines.push('')
|
96
113
|
cursor.offset += 1
|
97
114
|
|
98
|
-
document = new Written.Document(lines.join('\n'))
|
115
|
+
document = new Written.Document(lines.join('\n'), @parsers)
|
99
116
|
document.cursor = cursor
|
100
117
|
if cursor.offset < document.toString().length
|
101
118
|
cursor.offset += 1
|
@@ -135,3 +152,4 @@ class @Written
|
|
135
152
|
texts.join '\n'
|
136
153
|
|
137
154
|
|
155
|
+
Written.Uploaders = {}
|
@@ -12,11 +12,14 @@ class Written.Cursor
|
|
12
12
|
parent = node.parentElement
|
13
13
|
|
14
14
|
if parent
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
child = node.previousSibling
|
16
|
+
|
17
|
+
while child
|
18
|
+
@offset += Written.Stringify(child).length
|
19
|
+
child = child.previousSibling
|
20
|
+
|
21
|
+
if node instanceof HTMLLIElement
|
22
|
+
this.offset += Array.prototype.indexOf.call(parent.children, node)
|
20
23
|
|
21
24
|
node = parent
|
22
25
|
|
@@ -24,7 +27,7 @@ class Written.Cursor
|
|
24
27
|
for child in @element().children
|
25
28
|
if child == node
|
26
29
|
break
|
27
|
-
@offset +=
|
30
|
+
@offset += Written.Stringify(child).length
|
28
31
|
@offset += 1
|
29
32
|
|
30
33
|
@currentNode = ->
|
@@ -36,7 +39,7 @@ class Written.Cursor
|
|
36
39
|
|
37
40
|
element = @element().firstElementChild
|
38
41
|
while element && element != node
|
39
|
-
offset -=
|
42
|
+
offset -= Written.Stringify(element).length
|
40
43
|
element = element.nextElementSibling
|
41
44
|
|
42
45
|
offset
|
@@ -44,16 +47,34 @@ class Written.Cursor
|
|
44
47
|
focus: (offset, node) =>
|
45
48
|
if offset is undefined
|
46
49
|
offset = @offset
|
50
|
+
else
|
51
|
+
@offset = offset
|
47
52
|
|
48
53
|
if node is undefined
|
49
54
|
node = @element().firstElementChild
|
50
55
|
|
51
|
-
while node.nextElementSibling &&
|
52
|
-
offset -=
|
56
|
+
while node.nextElementSibling && Written.Stringify(node).length < offset
|
57
|
+
offset -= Written.Stringify(node).length + 1
|
53
58
|
node = node.nextElementSibling
|
54
59
|
|
55
60
|
|
56
|
-
range = node.getRange(Math.min(offset,
|
57
|
-
|
58
|
-
@selection
|
61
|
+
range = node.getRange(Math.min(offset, Written.Stringify(node).length), document.createTreeWalker(node, NodeFilter.SHOW_TEXT))
|
62
|
+
|
63
|
+
if @offsetDiffersBetween(@selection, range)
|
64
|
+
@selection.removeAllRanges()
|
65
|
+
@selection.addRange(range)
|
66
|
+
if !@inViewport(node)
|
67
|
+
node.scrollIntoView()
|
68
|
+
|
69
|
+
offsetDiffersBetween: (selection, range) ->
|
70
|
+
selection.focusNode != range.startContainer ||
|
71
|
+
selection.focusOffset != range.startOffset
|
72
|
+
|
73
|
+
inViewport: (node) ->
|
74
|
+
bounding = node.getBoundingClientRect()
|
75
|
+
|
76
|
+
bounding.top >= 0 &&
|
77
|
+
bounding.left >= 0 &&
|
78
|
+
bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
79
|
+
bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
|
59
80
|
|
@@ -1,20 +1,25 @@
|
|
1
1
|
#= require ../parsers/parsers
|
2
2
|
|
3
|
-
Written.Document
|
4
|
-
constructor: (textContent) ->
|
5
|
-
@
|
3
|
+
class Written.Document
|
4
|
+
constructor: (textContent, parsers) ->
|
5
|
+
@markdown = textContent
|
6
6
|
|
7
|
-
@
|
7
|
+
@blocks = parsers.block.parse(textContent.split('\n').reverse())
|
8
8
|
|
9
|
-
|
9
|
+
@blocks.forEach (block) =>
|
10
|
+
block.processContent(parsers.inline.parse.bind(parsers.inline))
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@texts.push(text)
|
15
|
-
node = node.nextDocumentNode
|
12
|
+
freeze: =>
|
13
|
+
Object.freeze(@blocks)
|
14
|
+
Object.freeze(@cursor)
|
16
15
|
|
16
|
+
toHTMLString: =>
|
17
|
+
text = ''
|
17
18
|
|
19
|
+
@blocks.forEach (node) ->
|
20
|
+
text += node.html().outerHTML + "\n"
|
21
|
+
|
22
|
+
text
|
18
23
|
|
19
24
|
toString: =>
|
20
|
-
@
|
25
|
+
@markdown
|
@@ -1,5 +1,6 @@
|
|
1
1
|
Written.History = class History
|
2
2
|
constructor: (document) ->
|
3
|
+
document.freeze()
|
3
4
|
@current = document
|
4
5
|
|
5
6
|
push: (document) =>
|
@@ -8,6 +9,7 @@ Written.History = class History
|
|
8
9
|
previousDocument.nextDocument = document
|
9
10
|
document.previousDocument = previousDocument
|
10
11
|
|
12
|
+
document.freeze()
|
11
13
|
@limit()
|
12
14
|
|
13
15
|
previous: =>
|