written 0.0.5 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/LICENSE +8 -0
  4. data/README.md +63 -0
  5. data/Rakefile +17 -27
  6. data/lib/written.rb +0 -8
  7. data/lib/written/app/assets/images/written/placeholder.png +0 -0
  8. data/lib/written/app/assets/javascripts/written.coffee +2 -0
  9. data/lib/written/app/assets/javascripts/written/core/content.coffee +53 -35
  10. data/lib/written/app/assets/javascripts/written/core/cursor.coffee +33 -12
  11. data/lib/written/app/assets/javascripts/written/core/document.coffee +16 -11
  12. data/lib/written/app/assets/javascripts/written/core/extensions/string.coffee +9 -0
  13. data/lib/written/app/assets/javascripts/written/core/extensions/text.coffee +2 -0
  14. data/lib/written/app/assets/javascripts/written/core/history.coffee +2 -0
  15. data/lib/written/app/assets/javascripts/written/core/observer.coffee +6 -2
  16. data/lib/written/app/assets/javascripts/written/core/stringify.coffee +15 -0
  17. data/lib/written/app/assets/javascripts/written/parsers/block.coffee +69 -0
  18. data/lib/written/app/assets/javascripts/written/parsers/block/code.coffee +79 -15
  19. data/lib/written/app/assets/javascripts/written/parsers/block/heading.coffee +60 -5
  20. data/lib/written/app/assets/javascripts/written/parsers/block/image.coffee +103 -9
  21. data/lib/written/app/assets/javascripts/written/parsers/block/olist.coffee +94 -12
  22. data/lib/written/app/assets/javascripts/written/parsers/block/paragraph.coffee +63 -5
  23. data/lib/written/app/assets/javascripts/written/parsers/block/quote.coffee +92 -0
  24. data/lib/written/app/assets/javascripts/written/parsers/block/ulist.coffee +93 -12
  25. data/lib/written/app/assets/javascripts/written/parsers/inline.coffee +81 -0
  26. data/lib/written/app/assets/javascripts/written/parsers/inline/code.coffee +57 -0
  27. data/lib/written/app/assets/javascripts/written/parsers/inline/italic.coffee +40 -7
  28. data/lib/written/app/assets/javascripts/written/parsers/inline/link.coffee +43 -13
  29. data/lib/written/app/assets/javascripts/written/parsers/inline/list.coffee +27 -0
  30. data/lib/written/app/assets/javascripts/written/parsers/inline/strong.coffee +41 -7
  31. data/lib/written/app/assets/javascripts/written/parsers/parsers.coffee +21 -107
  32. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/CustomElements.js +32 -0
  33. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/base.js +40 -0
  34. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/boot.js +124 -0
  35. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/observe.js +318 -0
  36. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/register.js +369 -0
  37. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/traverse.js +86 -0
  38. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/upgrade.js +130 -0
  39. data/lib/written/app/assets/javascripts/written/polyfills/MutationObserver/MutationObserver.js +575 -0
  40. data/lib/written/app/assets/javascripts/written/polyfills/WeakMap/WeakMap.js +49 -0
  41. data/lib/written/app/assets/javascripts/written/polyfills/base.coffee +10 -0
  42. data/lib/written/app/assets/javascripts/written/polyfills/dom.js +104 -0
  43. data/lib/written/app/assets/javascripts/written/uploaders/aws.coffee +125 -0
  44. data/lib/written/app/assets/stylesheets/written.scss +80 -11
  45. data/lib/written/version.rb +1 -1
  46. data/test/server/app/assets/javascripts/application.coffee +20 -2
  47. data/test/server/app/assets/stylesheets/application.scss +2 -2
  48. data/test/server/app/assets/stylesheets/prism.css +0 -1
  49. data/test/server/app/views/posts/show.html.erb +10 -3
  50. metadata +26 -20
  51. data/lib/written/app/assets/javascripts/written/core/ext.coffee +0 -109
  52. data/lib/written/app/assets/javascripts/written/core/extensions.coffee +0 -2
  53. data/lib/written/document.rb +0 -42
  54. data/lib/written/node.rb +0 -21
  55. data/lib/written/nodes/code.rb +0 -65
  56. data/lib/written/nodes/heading.rb +0 -15
  57. data/lib/written/nodes/image.rb +0 -14
  58. data/lib/written/nodes/ordered_list.rb +0 -18
  59. data/lib/written/nodes/unordered_list.rb +0 -19
  60. data/lib/written/parsers.rb +0 -11
  61. data/lib/written/parsers/base.rb +0 -26
  62. data/lib/written/parsers/code.rb +0 -60
  63. data/lib/written/parsers/heading.rb +0 -19
  64. data/lib/written/parsers/image.rb +0 -19
  65. data/lib/written/parsers/link.rb +0 -12
  66. data/lib/written/parsers/list.rb +0 -33
  67. data/lib/written/parsers/word.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0d2af27e114cad6a824e7f2c2201ae14845c0349
4
- data.tar.gz: 69cae4da7a9f399e5446a2cd73363fdbee7c5569
3
+ metadata.gz: 7ba35767946cb9b007468b0ba15fcd1d154f627b
4
+ data.tar.gz: 7bc3ccaab8687dc4a17bc52322d088a75af27fe6
5
5
  SHA512:
6
- metadata.gz: 91130fcec0ee2203c6b204266a3c744aed82ca80695d4fcf18b4084c63f937d6c4f172045b6474d1d31b46e6c336ac6db83ae19daafea5e02df54f4a91b66434
7
- data.tar.gz: cfe89593f4956402fd8db8e36f4fbcf77cfcbb859f0e5cf5259d845cc0d79ffc8042de5694641b7fb3b3ea84cb250d8003c425776fabe8f153850b0dabc0134c
6
+ metadata.gz: f19e09c7f1fd9f2993231535a1bdf10119c4a412950cb0152992366385a4294220b674aed556708cfca13ae895744876451eb617aaf2ff3b9691647df624f468
7
+ data.tar.gz: 421434a2a0bfb187357ec99aa9c14be898921137218971015552291d283cd4e610896ecdc316e7d24cf07ec705afb09abe727cd9ec33cdcee5d7c3c7c1907393
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  .DS_Store
2
2
  tmp/
3
+ secrets.*
3
4
  *.log
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
@@ -1,12 +1,4 @@
1
1
  module Written
2
- autoload :Document, 'written/document'
3
- autoload :Version, 'written/version'
4
-
5
- def self.parse(str)
6
- document = Document.new(str)
7
- document.parse!
8
- document
9
- end
10
2
  end
11
3
 
12
4
  if defined?(Rails)
@@ -1,2 +1,4 @@
1
+ #= require ./written/polyfills/base
1
2
  #= require_tree ./written/core
2
3
  #= require_tree ./written/parsers
4
+ #= require_tree ./written/uploaders
@@ -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
- @observer.pause @initialize
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
- initialize: =>
17
- if @parsers?
18
- return
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
- Written.Parsers.freeze()
33
+ if @element().contentEditable != 'true'
34
+ @element().contentEditable = 'true'
21
35
 
22
- document = new Written.Document(@toString())
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
- node = @history.current.head
43
+ @update(document)
44
+ document.cursor.focus(document.toString().length)
29
45
 
30
- @element().textContent = ''
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
- document.cursor.focus(0, @element())
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
- node = document.head
51
- cursor = document.cursor
52
- current = @element().firstElementChild
74
+ elements = Array.prototype.slice.call(@element().children)
53
75
 
54
- while node
55
- if !current?
56
- @element().appendChild(node.cloneNode(true))
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
- node = node.nextDocumentNode
63
- if current?
64
- current = current.nextElementSibling
80
+ if element?
81
+ if !block.identical(element, node)
82
+ @element().replaceChild(node, element)
83
+ else
84
+ @element().appendChild(node)
65
85
 
66
- if current?
67
- node = current
68
- while node
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
- for child in (parent.childNodes || [])
16
- if child == node
17
- break
18
- else
19
- @offset += child.toString().length
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 += child.toString().length
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 -= element.toString().length
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 && node.toString().length < offset
52
- offset -= node.toString().length + 1
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, node.toString().length), document.createTreeWalker(node, NodeFilter.SHOW_TEXT))
57
- @selection.removeAllRanges()
58
- @selection.addRange(range)
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 = class
4
- constructor: (textContent) ->
5
- @texts = new Array()
3
+ class Written.Document
4
+ constructor: (textContent, parsers) ->
5
+ @markdown = textContent
6
6
 
7
- @head = Written.Parsers.Block.parse(textContent.split('\n').reverse())
7
+ @blocks = parsers.block.parse(textContent.split('\n').reverse())
8
8
 
9
- node = @head
9
+ @blocks.forEach (block) =>
10
+ block.processContent(parsers.inline.parse.bind(parsers.inline))
10
11
 
11
- while node
12
- Written.Parsers.Inline.parse(node)
13
- text = node.toString()
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
- @texts.join('\n')
25
+ @markdown
@@ -0,0 +1,9 @@
1
+ String::toHTML = ->
2
+ el = document.createElement('div')
3
+ el.innerHTML = this
4
+ if el.children.length > 1
5
+ el.children
6
+ else
7
+ el.children[0]
8
+
9
+
@@ -0,0 +1,2 @@
1
+ Text::toString = ->
2
+ @textContent
@@ -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: =>