diff --git a/.eslintrc b/.eslintrc
index 7b1047b..04c4e6b 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,17 +1,17 @@
-{ "parser": "babel-eslint"
-, "ecmaFeatures": { "modules": true }
-, "env":
- { "browser": true
- , "node": true
- , "es6": true
- }
-, "rules":
- { "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 1}]
- , "no-console": 2
- , "comma-style": [2, "first"]
- , "camelcase": 0
- , "indent": 0
- }
-, "plugins": ["react"]
-, "extends": ["standard", "plugin:react/recommended"]
-}
\ No newline at end of file
+{
+ "parser": "babel-eslint",
+ "ecmaFeatures": { "modules": true },
+ "env": {
+ "browser": true,
+ "node": true,
+ "es6": true
+ },
+ "rules": {
+ "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 1}],
+ "comma-dangle": [2, "always-multiline"],
+ "no-console": 2,
+ "camelcase": 0
+ },
+ "plugins": ["react"],
+ "extends": ["standard", "plugin:react/recommended"]
+}
diff --git a/.gitignore b/.gitignore
index 95ed199..6667f68 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,5 @@ logs
node_modules
dist
.DS_Store
+
+*.orig
diff --git a/.travis.yml b/.travis.yml
index 69ca89f..f86b46c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
language: node_js
node_js:
-- 6.9
+- 7.4
before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
diff --git a/CHANGES.md b/CHANGES.md
index 529ecf0..abb483c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,83 @@
### dev
+# 2.0.0 - 2017-01-25
+
+* Some style tweaks
+* All of these:
+
+#### 2.0.0-11 prerelease - 2017-01-24
+
+* Merge `featureFlags` prop from 1.4.x
+* ed.version (#306)
+* Link input `type="url"` for mobile input keyboard
+* Show attributions in inline view
+* Show block type & link in inline view
+* `hr` padding for easier selection
+
+#### 2.0.0-10 prerelease - 2017-01-21
+
+* Node v7.4.0
+
+#### 2.0.0-9 prerelease - 2017-01-21
+
+* Clarify link placeholder
+* iframe widget name + drag handle
+* Click to select simplification
+* Delete block wording & close modal
+* No text select draggable widgets
+* Enter in text area to close modal and menu
+* Modal style
+* Fix bug where clicking "Edit" would select node and jump away from pointer
+* Default `coverPrefs`
+* Fix blur: prevents delete by backspace from "under" modal
+
+#### 2.0.0-8 prerelease - 2017-01-19
+
+* Merge v1 patch
+* Fix bug that sometimes focused top of doc on clicking block "edit"
+
+#### 2.0.0-7 prerelease - 2017-01-19
+
+* Blur editable on modal open.
+* Don't allow typing over node selection. Fixes bug that media view can be typed over and deleted after clicking "edit."
+
+#### 2.0.0-6 prerelease - 2017-01-19
+
+* Modal media block meta editing! :tada:
+* Fixed menu hack _only_ on iOS
+
+#### 2.0.0-5 prerelease - 2017-01-10
+
+* Fix gh-pages widget serving (.nojekyll)
+* Fix link menu form position
+* Fix file drops: on both media blocks & text
+
+#### 2.0.0-4 prerelease - 2017-01-10
+
+* ProseMirror 0.17.0
+* URL modal attached to menu
+* URL pre-filled if selected text is link-like (#288)
+
+#### 2.0.0-3 prerelease - 2017-01-05
+
+* bump imgflo-url to ignore `blob:` URLs
+
+#### 2.0.0-2 prerelease - 2017-01-05
+
+* fix `menuBar: false`
+
+#### 2.0.0-1 prerelease - 2017-01-05
+
+* BREAKING -- `mountApp` is async because of a React change, and does not return `ed`
+ * `props.onMount` callback is called with `ed` instance
+
+#### 2.0.0-0 prerelease - 2017-01-02
+
+* ProseMirror 0.16.0 -- big refactor to get up to date
+ * Redo all plugins & everything we have built on PM
+* Widgets are inline: major simplification
+* h1-h3 empty block placeholders
+
### 1.4.2 - 2017-01-23
* `featureFlags: {edCta, edEmbed}` to match API
diff --git a/README.md b/README.md
index 09018af..345e725 100644
--- a/README.md
+++ b/README.md
@@ -48,7 +48,7 @@ available to use like this:
``` js
var container = document.querySelector('#ed')
- var ed = window.TheGridEd.mountApp(container, {
+ window.TheGridEd.mountApp(container, {
// REQUIRED -- Content array from post
initialContent: [],
// OPTIONAL (default true) enable or disable the default menu
@@ -94,8 +94,9 @@ available to use like this:
/* Once upload is complete, app hits ed.setCoverSrc */
},
// OPTIONAL
- onMount: function () {
+ onMount: function (mounted) {
/* Called once PM and widgets are mounted */
+ window.ed = mounted
},
// OPTIONAL
onCommandsChanged: function (commands) {
diff --git a/demo/demo.js b/demo/demo.js
index 738858b..c97a57a 100644
--- a/demo/demo.js
+++ b/demo/demo.js
@@ -22,32 +22,31 @@ function setup (options) {
apiJSON.value = JSON.stringify(options.initialContent, null, 2)
}
const props =
- { initialContent: (options.initialContent || [])
- , onChange: () => { console.log('change') }
- , onMount: () => { console.log('mount') }
- , onShareFile: onShareFileDemo
- , onShareUrl: onShareUrlDemo
- , onRequestCoverUpload: onRequestCoverUploadDemo
- , onPlaceholderCancel: onPlaceholderCancelDemo
- , onCommandsChanged: (commands) => {}
- , onDropFiles: onDropFilesDemo
- , onDropFileOnBlock: onDropFileOnBlockDemo
- , imgfloConfig: null
- , widgetPath: './node_modules/'
- , coverPrefs: { filter: false }
- , menuBar: true
- , featureFlags:
- { edCta: false
- , edEmbed: false
- }
- , ref:
- function (mounted) {
- ed = mounted
- console.log(ed)
- window.ed = ed
- }
+ { initialContent: (options.initialContent || []),
+ onChange: () => { console.log('onChange') },
+ onMount: function (mounted) {
+ ed = mounted
+ console.log(ed)
+ window.ed = ed
+ },
+ onShareFile: onShareFileDemo,
+ onShareUrl: onShareUrlDemo,
+ onRequestCoverUpload: onRequestCoverUploadDemo,
+ onPlaceholderCancel: onPlaceholderCancelDemo,
+ onCommandsChanged: function (commands) {
+ // console.log(commands)
+ },
+ onDropFiles: onDropFilesDemo,
+ onDropFileOnBlock: onDropFileOnBlockDemo,
+ imgfloConfig: null,
+ widgetPath: './node_modules/',
+ coverPrefs: { filter: false },
+ menuBar: true,
+ featureFlags: {
+ edCta: false,
+ edEmbed: false,
+ },
}
-
mountApp(container, props)
// Only for fixture demo
@@ -117,10 +116,10 @@ function filesUploadSim (index, files) {
const updatedBlocks = ids.map(function (id, index) {
ed.updateProgress(id, {progress: null})
return (
- { id
- , type: 'image'
- , metadata: {title: names[index]}
- }
+ { id,
+ type: 'image',
+ metadata: {title: names[index]},
+ }
)
})
ed.setContent(updatedBlocks)
@@ -146,29 +145,29 @@ function onShareUrlDemo (share) {
function () {
console.log('Share: mount block')
ed.setContent([
- { id: block
- , type: 'article'
- , metadata:
- { title: 'Shared article title'
- , description: `Simulated share from ${url}`
- }
- }
+ { id: block,
+ type: 'article',
+ metadata:
+ { title: 'Shared article title',
+ description: `Simulated share from ${url}`,
+ },
+ },
])
window.setTimeout(function () {
console.log('Share: mount block + cover')
ed.setContent([
- { id: block
- , type: 'article'
- , metadata:
- { title: 'Shared article title + cover'
- , description: `Simulated share from ${url}`
- }
- , cover:
- { src: 'http://meemoo.org/images/meemoo-illo-by-jyri-pieniniemi-400.png'
- , width: 400
- , height: 474
- }
- }
+ { id: block,
+ type: 'article',
+ metadata:
+ { title: 'Shared article title + cover',
+ description: `Simulated share from ${url}`,
+ },
+ cover:
+ { src: 'http://meemoo.org/images/meemoo-illo-by-jyri-pieniniemi-400.png',
+ width: 400,
+ height: 474,
+ },
+ },
])
}, 1000)
}
diff --git a/demo/fixture.js b/demo/fixture.js
index cee08c6..bafee16 100644
--- a/demo/fixture.js
+++ b/demo/fixture.js
@@ -1,5 +1,3 @@
-/* eslint quotes: [0], comma-style: [0] */
-
import getHappyLittlePhrase from 'bob-ross-lipsum'
let tweet = {
@@ -24,26 +22,26 @@ let tweet = {
[240, 243, 244],
[17, 17, 20],
[103, 117, 134],
- [186, 188, 191]
- ]
- }
+ [186, 188, 191],
+ ],
+ },
}],
'related': [],
'publisher': {
'url': 'http://twitter.com',
'name': 'Twitter',
'favicon': 'https://abs.twimg.com/favicons/favicon.ico',
- 'domain': 'twitter.com'
+ 'domain': 'twitter.com',
},
'keywords': ['jasonfried', 'thegridio', 'https', 'thegrid', 'dracula_x', 'conceptually', 'location', 'websites', 'sure', 'waynepelletier'],
- // 'description': 'https://thegrid.io - "AI" website design. Conceptually feels very next level, an obvious, natural progression just waiting to happen.',
+ 'description': 'https://thegrid.io - "AI" website design. Conceptually feels very next level, an obvious, natural progression just waiting to happen.',
'inLanguage': 'English',
'@type': 'Comment',
'app_links': [],
'@context': 'http://schema.org',
'isBasedOnUrl': 'https://twitter.com/jasonfried/status/522492212144525312',
- 'source': '926db660-ed6c-43f6-b838-56ac6a527034'
- }
+ 'source': '926db660-ed6c-43f6-b838-56ac6a527034',
+ },
}
let imageRaphael = {
@@ -67,33 +65,33 @@ let imageRaphael = {
[
229,
212,
- 218
+ 218,
],
[
185,
162,
- 160
+ 160,
],
[
81,
70,
- 64
+ 64,
],
[
139,
118,
- 116
- ]
- ]
- }
- }
+ 116,
+ ],
+ ],
+ },
+ },
],
'related': [],
'publisher': {
'url': 'http://twitter.com',
'name': 'Twitter',
'favicon': 'https://abs.twimg.com/favicons/favicon.ico',
- 'domain': 'twitter.com'
+ 'domain': 'twitter.com',
},
'keywords': [
'raphael',
@@ -105,7 +103,7 @@ let imageRaphael = {
'approx',
'deepforgery',
'360k',
- 'erbwqjyelg'
+ 'erbwqjyelg',
],
'description': 'StyleNet #NeuralArt, inspiration from @jeratt and style by Raphael.',
'inLanguage': 'en',
@@ -116,12 +114,12 @@ let imageRaphael = {
'datePublished': null,
'starred': true,
'caption': 'StyleNet #NeuralArt, inspiration from @jeratt and style by Raphael.',
- 'source': 'd6fddcde-0831-4058-83a8-110b03aab390'
+ 'source': 'd6fddcde-0831-4058-83a8-110b03aab390',
},
'caption': 'StyleNet #NeuralArt, inspiration from @jeratt and style by Raphael.',
'cover': {
- 'src': 'https://pbs.twimg.com/media/CODJ8KXWoAAVGTP.jpg:large'
- }
+ 'src': 'https://pbs.twimg.com/media/CODJ8KXWoAAVGTP.jpg:large',
+ },
}
let videoTurtle = {
@@ -137,15 +135,15 @@ let videoTurtle = {
{
'name': 'Jon Nordby',
'url': 'http://www.youtube.com/channel/UCB9kP5NQGu0JLWa9UlkxklQ',
- 'avatar': {}
- }
+ 'avatar': {},
+ },
],
'related': [],
'publisher': {
'url': 'http://www.youtube.com/',
'name': 'YouTube',
'favicon': 'https://s.ytimg.com/yts/img/favicon-vfldLzJxy.ico',
- 'domain': 'www.youtube.com'
+ 'domain': 'www.youtube.com',
},
'keywords': [
'duration',
@@ -157,7 +155,7 @@ let videoTurtle = {
'ravensbourne',
'mozfest',
'milling',
- 'jens'
+ 'jens',
],
'description': getHappyLittlePhrase(),
'inLanguage': 'English',
@@ -167,27 +165,27 @@ let videoTurtle = {
'url': 'vnd.youtube://www.youtube.com/watch?v=IMShMTn8yEU&feature=applinks',
'type': 'ios',
'app_store_id': '544007664',
- 'app_name': 'YouTube'
+ 'app_name': 'YouTube',
},
{
'url': 'http://www.youtube.com/watch?v=IMShMTn8yEU&feature=applinks',
'type': 'android',
'app_name': 'YouTube',
- 'package': 'com.google.android.youtube'
+ 'package': 'com.google.android.youtube',
},
{
'url': 'http://www.youtube.com/watch?v=IMShMTn8yEU&feature=applinks',
- 'type': 'web'
- }
+ 'type': 'web',
+ },
],
'@context': 'http://schema.org',
'isBasedOnUrl': 'https://www.youtube.com/watch?v=IMShMTn8yEU',
'title': getHappyLittlePhrase(),
'starred': false,
- 'source': '77d2788d-d7c5-4fbe-9087-4328d9f12ddb'
+ 'source': '77d2788d-d7c5-4fbe-9087-4328d9f12ddb',
},
'video': {
- 'src': 'https://cdn.embedly.com/widgets/media.html?src=http%3A%2F%2Fwww.youtube.com%2Fembed%2FIMShMTn8yEU%3Ffeature%3Doembed&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DIMShMTn8yEU&image=http%3A%2F%2Fi.ytimg.com%2Fvi%2FIMShMTn8yEU%2Fhqdefault.jpg&key=b7d04c9b404c499eba89ee7072e1c4f7&type=text%2Fhtml&schema=youtube'
+ 'src': 'https://cdn.embedly.com/widgets/media.html?src=http%3A%2F%2Fwww.youtube.com%2Fembed%2FIMShMTn8yEU%3Ffeature%3Doembed&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DIMShMTn8yEU&image=http%3A%2F%2Fi.ytimg.com%2Fvi%2FIMShMTn8yEU%2Fhqdefault.jpg&key=b7d04c9b404c499eba89ee7072e1c4f7&type=text%2Fhtml&schema=youtube',
},
'cover': {
'orientation': 'landscape',
@@ -196,9 +194,9 @@ let videoTurtle = {
'width': 480,
'height': 360,
'faces': [],
- 'colors': [[47, 47, 48], [213, 215, 206], [123, 119, 115], [149, 162, 164], [136, 144, 159]]
+ 'colors': [[47, 47, 48], [213, 215, 206], [123, 119, 115], [149, 162, 164], [136, 144, 159]],
},
- 'title': 'Mozfest 2014 - Turtle Power: Mirobot and Flowhub penplotting'
+ 'title': 'Mozfest 2014 - Turtle Power: Mirobot and Flowhub penplotting',
}
let article = {
@@ -218,14 +216,14 @@ let article = {
'url': 'http://www.wired.com/2015/01/orchestra-tiny-humming-robots-conduct-gestures/',
'thumbnail_height': 750,
'thumbnail_url': 'http://www.wired.com/wp-content/uploads/2015/01/IMG_5196.jpg',
- 'thumbnail_width': 1000
- }
+ 'thumbnail_width': 1000,
+ },
],
'publisher': {
'url': 'http://meemoo.org',
'name': 'Meemoo',
'favicon': 'http://meemoo.org/favicon.ico',
- 'domain': 'meemoo.org'
+ 'domain': 'meemoo.org',
},
'keywords': [
'mirobot',
@@ -237,7 +235,7 @@ let article = {
'noflo',
'art',
'workshops',
- 'graph'
+ 'graph',
],
'description': getHappyLittlePhrase(5),
'inLanguage': 'English',
@@ -248,7 +246,7 @@ let article = {
'title': getHappyLittlePhrase(),
'starred': false,
'caption': "14 Jan 2015 by Vilson Vieira After working for about three years with Forrest we finally meet him on a meet up of The Grid team. During the first days we were preparing a workshop for MozFest's #ArtOfWeb track. The idea was to present a quick introduction to Flowhub/ NoFlo and how to use it to draw with Mirobot.",
- 'source': 'ad25432f-11f9-4326-8b0c-edbffa7afbdc'
+ 'source': 'ad25432f-11f9-4326-8b0c-edbffa7afbdc',
},
'cover': {
'orientation': 'landscape',
@@ -257,10 +255,10 @@ let article = {
'width': 1206,
'height': 511,
'faces': [],
- 'colors': [[7, 8, 8], [177, 178, 177], [251, 4, 28], [14, 208, 125], [21, 106, 194]]
+ 'colors': [[7, 8, 8], [177, 178, 177], [251, 4, 28], [14, 208, 125], [21, 106, 194]],
},
'title': 'Turtle power to the people',
- 'caption': "14 Jan 2015 by Vilson Vieira After working for about three years with Forrest we finally meet him on a meet up of The Grid team. During the first days we were preparing a workshop for MozFest's #ArtOfWeb track. The idea was to present a quick introduction to Flowhub/ NoFlo and how to use it to draw with Mirobot."
+ 'caption': "14 Jan 2015 by Vilson Vieira After working for about three years with Forrest we finally meet him on a meet up of The Grid team. During the first days we were preparing a workshop for MozFest's #ArtOfWeb track. The idea was to present a quick introduction to Flowhub/ NoFlo and how to use it to draw with Mirobot.",
}
let imageCole = {
@@ -277,18 +275,18 @@ let imageCole = {
'@type': 'Article',
'publisher': {
'domain': 'the-grid-user-content.s3-us-west-2.amazonaws.com',
- 'name': 'The Grid'
+ 'name': 'The Grid',
},
'starred': true,
'source': '51642a12-50cf-4072-855c-d7d8294ba125',
author: [{
name: 'Cole Rise',
- url: 'https://cole.grid/'
+ url: 'https://cole.grid/',
}],
title: getHappyLittlePhrase(),
coverPrefs: {
- filter: false
- }
+ filter: false,
+ },
},
'cover': {
'orientation': 'landscape',
@@ -302,9 +300,9 @@ let imageCole = {
'polygon': [[1219, 259], [1213, 254], [1190, 251], [1178, 264], [1151, 266], [1130, 275], [1113, 293], [1045, 293], [960, 282], [917, 282], [838, 292], [824, 302], [767, 311], [766, 322], [709, 318], [621, 341], [550, 384], [492, 410], [471, 424], [466, 434], [453, 434], [382, 469], [340, 469], [285, 446], [268, 449], [267, 456], [276, 457], [278, 480], [307, 484], [311, 571], [272, 576], [267, 607], [313, 604], [339, 621], [366, 624], [367, 658], [464, 641], [467, 635], [532, 619], [532, 606], [610, 598], [641, 591], [728, 587], [834, 571], [914, 569], [987, 578], [998, 585], [1025, 585], [1027, 591], [1050, 593], [1051, 599], [1083, 599], [1084, 611], [1119, 611], [1127, 624], [1144, 615], [1184, 615], [1192, 636], [1204, 630], [1206, 619], [1215, 614], [1215, 540], [1232, 534], [1232, 527], [1180, 527], [1180, 487], [1199, 469], [1190, 447], [1190, 406], [1197, 405], [1200, 367], [1216, 366], [1208, 341], [1208, 298], [1217, 295], [1223, 282]],
'center': [743, 433],
'radius': 522.01,
- 'bounding_rect': [[267, 251], [1233, 659]]
- }
- }
+ 'bounding_rect': [[267, 251], [1233, 659]],
+ },
+ },
}
let code = {
@@ -314,12 +312,12 @@ let code = {
'metadata': {
'title': 'code code',
'description': 'very sample code',
- 'programmingLanguage': 'text/coffeescript'
+ 'programmingLanguage': 'text/coffeescript',
},
'src': null,
'text': '# Assignment:\nnumber = 42\nopposite = true\n\n# Conditions:\nnumber = -42 if opposite\n\n# Functions:\nsquare = (x) -> x * x\n\n# Arrays:\nlist = [1, 2, 3, 4, 5]\n\n# Objects:\nmath =\n root: Math.sqrt\n square: square\n cube: (x) -> x * square x\n\n# Splats:\nrace = (winner, runners...) ->\n print winner, runners\n\n# Existence:\nalert "I knew it!" if elvis?\n\n# Array comprehensions:\ncubes = (math.cube num for num in list)',
'length': 32,
- 'measurementVersion': 5
+ 'measurementVersion': 5,
}
let imageBeingD4 = {
@@ -342,17 +340,17 @@ let imageBeingD4 = {
'title': getHappyLittlePhrase(),
'author': [
{
- 'name': 'Gordon'
- }
+ 'name': 'Gordon',
+ },
],
'authors': [],
'publisher': {
'name': 'i.meemoo.me',
'domain': 'i.meemoo.me',
'url': null,
- 'favicon': null
+ 'favicon': null,
},
- 'user': '79922a3a-1dcb-4a43-9b62-e4f3af6ad4ca'
+ 'user': '79922a3a-1dcb-4a43-9b62-e4f3af6ad4ca',
},
'title': 'being d4',
'cover': {
@@ -361,13 +359,13 @@ let imageBeingD4 = {
[144, 126, 104],
[50, 39, 37],
[225, 208, 192],
- [108, 90, 68]
+ [108, 90, 68],
],
'saliency': {
'polygon': [[1350, 300], [600, 200], [100, 250], [200, 600], [1250, 700]],
'center': [720, 480],
'radius': 700,
- 'bounding_rect': [[100, 200], [1350, 700]]
+ 'bounding_rect': [[100, 200], [1350, 700]],
},
'src': 'http://i.meemoo.me/v1/in/Adx3TWdBQ3O3uJvyH2DP_being-d4.jpg',
'orientation': 'landscape',
@@ -382,7 +380,7 @@ let imageBeingD4 = {
'width': 60.181420320681795,
'height': 60.181420320681795,
'neighbors': 11,
- 'confidence': 8.715965329999982
+ 'confidence': 8.715965329999982,
},
{
'x': 367.5215516831421,
@@ -390,7 +388,7 @@ let imageBeingD4 = {
'width': 54.27794792754907,
'height': 54.27794792754907,
'neighbors': 14,
- 'confidence': 8.305308969999993
+ 'confidence': 8.305308969999993,
},
{
'x': 492.2391919725017,
@@ -398,7 +396,7 @@ let imageBeingD4 = {
'width': 57.606293506030504,
'height': 57.606293506030504,
'neighbors': 12,
- 'confidence': 7.161568390000004
+ 'confidence': 7.161568390000004,
},
{
'x': 486.83705737694504,
@@ -406,7 +404,7 @@ let imageBeingD4 = {
'width': 50.952088047919155,
'height': 50.952088047919155,
'neighbors': 12,
- 'confidence': 6.479253949999998
+ 'confidence': 6.479253949999998,
},
{
'x': 296.1169884519212,
@@ -414,7 +412,7 @@ let imageBeingD4 = {
'width': 67.83546137450323,
'height': 67.83546137450323,
'neighbors': 13,
- 'confidence': 6.356675570000005
+ 'confidence': 6.356675570000005,
},
{
'x': 646.9246222261988,
@@ -422,7 +420,7 @@ let imageBeingD4 = {
'width': 59.64585507428254,
'height': 59.64585507428254,
'neighbors': 5,
- 'confidence': 6.261280809999998
+ 'confidence': 6.261280809999998,
},
{
'x': 364.884811041297,
@@ -430,7 +428,7 @@ let imageBeingD4 = {
'width': 66.86416232804767,
'height': 66.86416232804767,
'neighbors': 5,
- 'confidence': 5.530725450000008
+ 'confidence': 5.530725450000008,
},
{
'x': 1224.101870760976,
@@ -438,7 +436,7 @@ let imageBeingD4 = {
'width': 74.9664382734353,
'height': 74.9664382734353,
'neighbors': 5,
- 'confidence': 5.0318680700000025
+ 'confidence': 5.0318680700000025,
},
{
'x': 958.9959594409227,
@@ -446,7 +444,7 @@ let imageBeingD4 = {
'width': 59.64585507428254,
'height': 59.64585507428254,
'neighbors': 5,
- 'confidence': 4.866107930000008
+ 'confidence': 4.866107930000008,
},
{
'x': 173.1109328273247,
@@ -454,7 +452,7 @@ let imageBeingD4 = {
'width': 58.86220762496542,
'height': 58.86220762496542,
'neighbors': 9,
- 'confidence': 4.741367449999998
+ 'confidence': 4.741367449999998,
},
{
'x': 462.6463818447289,
@@ -462,7 +460,7 @@ let imageBeingD4 = {
'width': 61.23303015148604,
'height': 61.23303015148604,
'neighbors': 10,
- 'confidence': 3.994080369999999
+ 'confidence': 3.994080369999999,
},
{
'x': 992.4931228509183,
@@ -470,7 +468,7 @@ let imageBeingD4 = {
'width': 65.59088857569006,
'height': 65.59088857569006,
'neighbors': 7,
- 'confidence': 3.941819850000007
+ 'confidence': 3.941819850000007,
},
{
'x': 594.709265637875,
@@ -478,7 +476,7 @@ let imageBeingD4 = {
'width': 63.90080839651627,
'height': 63.90080839651627,
'neighbors': 5,
- 'confidence': 1.0505017100000016
+ 'confidence': 1.0505017100000016,
},
{
'x': 1049.8796264067955,
@@ -486,7 +484,7 @@ let imageBeingD4 = {
'width': 60.997264465222855,
'height': 60.997264465222855,
'neighbors': 4,
- 'confidence': 0.9801972499999992
+ 'confidence': 0.9801972499999992,
},
{
'x': 1034.9838867187502,
@@ -494,7 +492,7 @@ let imageBeingD4 = {
'width': 68.15576171875001,
'height': 68.15576171875001,
'neighbors': 1,
- 'confidence': 0.57032285
+ 'confidence': 0.57032285,
},
{
'x': 245.90400995259034,
@@ -502,7 +500,7 @@ let imageBeingD4 = {
'width': 40.877432949014576,
'height': 40.877432949014576,
'neighbors': 2,
- 'confidence': -0.4849196699999989
+ 'confidence': -0.4849196699999989,
},
{
'x': 1049.9172332545004,
@@ -510,7 +508,7 @@ let imageBeingD4 = {
'width': 61.197989614635624,
'height': 61.197989614635624,
'neighbors': 2,
- 'confidence': -1.4843781100000037
+ 'confidence': -1.4843781100000037,
},
{
'x': 1092.2399786737249,
@@ -518,7 +516,7 @@ let imageBeingD4 = {
'width': 76.4162095711182,
'height': 76.4162095711182,
'neighbors': 1,
- 'confidence': -1.75232689
+ 'confidence': -1.75232689,
},
{
'x': 741.3058376715854,
@@ -526,7 +524,7 @@ let imageBeingD4 = {
'width': 54.240217510521234,
'height': 54.240217510521234,
'neighbors': 1,
- 'confidence': -2.690027709999997
+ 'confidence': -2.690027709999997,
},
{
'x': 867.1901815303999,
@@ -534,10 +532,10 @@ let imageBeingD4 = {
'width': 48.39919881847384,
'height': 48.39919881847384,
'neighbors': 1,
- 'confidence': -4.276220189999993
- }
- ]
- }
+ 'confidence': -4.276220189999993,
+ },
+ ],
+ },
}
let sharing = {
@@ -546,35 +544,35 @@ let sharing = {
metadata: {
starred: true,
status: 'Sharing... https://thegrid.io/#8',
- progress: 67
- }
+ progress: 67,
+ },
}
let location = {
- "metadata": {
- "geo": {
- "latitude": 68.55260186877743,
- "longitude": 22.666168212890625,
- "zoom": 5
- },
- "address": "Enontekiö, Lappi, Finland",
- starred: true
+ 'metadata': {
+ 'geo': {
+ 'latitude': 68.55260186877743,
+ 'longitude': 22.666168212890625,
+ 'zoom': 5,
+ },
+ 'address': 'Enontekiö, Lappi, Finland',
+ starred: true,
},
- "id": "uuid-loca-tion",
- "html": "",
- "type": "location"
+ 'id': 'uuid-loca-tion',
+ 'html': '',
+ 'type': 'location',
}
let userhtml = {
- "type": "interactive",
- "html": "",
- "text": "
Hello 😬
\nworld 🌍
\n",
- "metadata": {
+ 'type': 'interactive',
+ 'html': '',
+ 'text': "Hello 😬
\nworld 🌍
\n",
+ 'metadata': {
starred: true,
- "widget": "userhtml",
- "isBasedOnUrl": "https://the-grid.github.io/ed-userhtml/?g=eJyzyTC080jNyclX-DB_xhobfSCXy6bArjy_KCcFKNTTa6NfABTJzE1XyM_LyU9MsVVKzs8rzs9J1StPLMrTUE8rys9VyMzLycxLVddUUiguSrZVyigpKbDS18_JL0rNLcisSM3RS87P1Tc3AqHkxJJiJTsAKJAp3Q"
+ 'widget': 'userhtml',
+ 'isBasedOnUrl': 'https://the-grid.github.io/ed-userhtml/?g=eJyzyTC080jNyclX-DB_xhobfSCXy6bArjy_KCcFKNTTa6NfABTJzE1XyM_LyU9MsVVKzs8rzs9J1StPLMrTUE8rys9VyMzLycxLVddUUiguSrZVyigpKbDS18_JL0rNLcisSM3RS87P1Tc3AqHkxJJiJTsAKJAp3Q',
},
- "id": "uuid-user-html"
+ 'id': 'uuid-user-html',
}
let cta = {
@@ -583,9 +581,9 @@ let cta = {
url: 'https://app.meemoo.org/',
label: 'Try it now!',
metadata: {
- starred: true
+ starred: true,
},
- id: 'uuid-cta'
+ id: 'uuid-cta',
}
let post = {
@@ -594,98 +592,98 @@ let post = {
'id': 'abc-00000000-p',
'type': 'text',
'html': `${getHappyLittlePhrase()}
Strong. Em. Both. Plain.
`,
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
{
'type': 'text',
'html': 'An unknown block type... important to keep place in doc flow:
',
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
{
type: 'unsupported-boo',
id: 'uuid-unsupported',
metadata: {
starred: true,
- isBasedOnUrl: 'https://meemoo.org/'
- }
+ isBasedOnUrl: 'https://meemoo.org/',
+ },
},
{
'type': 'text',
'html': 'An `hr` block divider:
',
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
{
'type': 'hr',
'html': '
',
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
{
'type': 'text',
'html': 'It\'s a link cta:
',
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
cta,
{
'type': 'text',
'html': 'Quote from tweet:
',
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
tweet,
{
'type': 'text',
'html': 'Look it\'s userhtml:
',
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
userhtml,
{
'type': 'text',
'html': 'Here\'s a location in Lapland:
',
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
location,
{
'type': 'text',
'html': 'Here\'s a normal image:
',
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
imageCole,
{
'type': 'text',
'html': 'Here\'s an article block with no cover (you can upload an image with ... menu):
',
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
{
'id': '0000-article',
'type': 'article',
'html': 'article title
',
- 'metadata': {'starred': true, 'title': 'article title', 'description': ''}
+ 'metadata': {'starred': true, 'title': 'article title', 'description': ''},
},
{
'type': 'text',
'html': 'Here\'s an article block with upload progress:
',
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
{
'id': '0000-article-progress',
'type': 'article',
'html': 'up
',
- 'metadata': {starred: true, title: 'up', description: '', progress: 66}
+ 'metadata': {starred: true, title: 'up', description: '', progress: 66},
},
{
'type': 'text',
'html': 'Here\'s an article block with failed upload:
',
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
{
'id': '0000-article-failed',
'type': 'article',
'html': 'boo
',
- 'metadata': {starred: true, title: 'boo', description: '', progress: 33, failed: true}
+ 'metadata': {starred: true, title: 'boo', description: '', progress: 33, failed: true},
},
{
'type': 'text',
'html': 'Here\'s an image block with unsalvageable cover:
',
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
{
'id': '0000-image-unsalvageable',
@@ -693,98 +691,98 @@ let post = {
'html': '',
'metadata': {starred: true, caption: 'Sometimes images go away :-('},
'cover':
- { unsalvageable: true
- , src: 'https://pbs.twimg.com/profile_images/674936695830282240/np255F6b_400x400.jpg'
- }
+ { unsalvageable: true,
+ src: 'https://pbs.twimg.com/profile_images/674936695830282240/np255F6b_400x400.jpg',
+ },
},
{
'type': 'text',
'html': 'Placeholder with preview:
',
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
{
'id': '0000-placeholder-preview',
'type': 'placeholder',
'html': '',
'metadata': {starred: true, status: 'Uploading...', progress: 85},
- 'cover': {src: 'https://pbs.twimg.com/profile_images/674936695830282240/np255F6b_400x400.jpg'}
+ 'cover': {src: 'https://pbs.twimg.com/profile_images/674936695830282240/np255F6b_400x400.jpg'},
},
{
'type': 'text',
'html': 'Placeholder for URL share with progress:
',
- 'metadata': {'starred': true}
+ 'metadata': {'starred': true},
},
sharing,
{
'type': 'text',
'html': 'Placeholder failed:
',
- 'metadata': {'starred': true}
- },
- { id: '0000-failed'
- , type: 'placeholder'
- , metadata:
- { starred: true
- , status: 'Hmmm...'
- , failed: true
- }
+ 'metadata': {'starred': true},
+ },
+ { id: '0000-failed',
+ type: 'placeholder',
+ metadata:
+ { starred: true,
+ status: 'Hmmm...',
+ failed: true,
+ },
},
{
'id': 'abc-00000000-h1',
'type': 'h1',
'html': `${getHappyLittlePhrase()}
`,
- 'metadata': {}
+ 'metadata': {},
},
{
'id': 'abc-00000000-blockquote',
'type': 'blockquote',
'html': `${getHappyLittlePhrase()}
`,
- 'metadata': {}
+ 'metadata': {},
},
code,
{
'id': 'abc-00000000-p2',
'type': 'p',
- 'html': `${getHappyLittlePhrase()}
`
+ 'html': `${getHappyLittlePhrase()}
`,
},
{
'id': 'uuid-broken-00',
- 'type': undefined
+ 'type': undefined,
},
imageRaphael,
videoTurtle,
{
'id': 'abc-00000000-h2',
'type': 'h2',
- 'html': `${getHappyLittlePhrase()}
`
+ 'html': `${getHappyLittlePhrase()}
`,
},
article,
{
'id': 'abc-00000000-h3',
'type': 'h3',
- 'html': `${getHappyLittlePhrase()}
`
+ 'html': `${getHappyLittlePhrase()}
`,
},
imageBeingD4,
{
'id': 'abc-00000000-02',
'type': 'text',
- 'html': `${getHappyLittlePhrase(2)}
`
+ 'html': `${getHappyLittlePhrase(2)}
`,
},
{
'id': 'abc-00000000-03',
'type': 'quote',
- 'html': `${getHappyLittlePhrase(3)}
`
+ 'html': `${getHappyLittlePhrase(3)}
`,
},
{
'id': 'abc-00000000-ol',
'type': 'list',
- 'html': `- ${getHappyLittlePhrase()}
- ${getHappyLittlePhrase()}
`
+ 'html': `- ${getHappyLittlePhrase()}
- ${getHappyLittlePhrase()}
`,
},
{
'id': 'abc-00000000-ul',
'type': 'list',
- 'html': `- ${getHappyLittlePhrase()}
- ${getHappyLittlePhrase()}
`
- }
- ]
+ 'html': `- ${getHappyLittlePhrase()}
- ${getHappyLittlePhrase()}
`,
+ },
+ ],
}
diff --git a/demo/gremlin-prosemirror.js b/demo/gremlin-prosemirror.js
index 5c7e5fc..e83d8c3 100644
--- a/demo/gremlin-prosemirror.js
+++ b/demo/gremlin-prosemirror.js
@@ -10,8 +10,8 @@ function randomFromArray (arr) {
const config =
- { logger: null
- , randomizer: null
+ { logger: null,
+ randomizer: null,
}
function pmSelecter (pm) {
@@ -65,13 +65,13 @@ function pmFormatter (pm) {
}
const subgremlins =
- [ pmSelecter
- , pmSelecterCollapsed
- , pmSelecterNode
- , pmTyper
- , pmFocuser
- , pmSplitter
- , pmFormatter
+ [ pmSelecter,
+ pmSelecterCollapsed,
+ pmSelecterNode,
+ pmTyper,
+ pmFocuser,
+ pmSplitter,
+ pmFormatter,
]
function pmGremlin () {
diff --git a/gh-pages.sh b/gh-pages.sh
index 33ed16c..d423151 100644
--- a/gh-pages.sh
+++ b/gh-pages.sh
@@ -17,6 +17,9 @@ mkdir dist/webpack
mv dist/demo.js dist/webpack/demo.js
mv dist/demo.map dist/webpack/demo.map
+# no need for jekyll in this demo (jekyll 3.3+ blocks node_modules)
+touch dist/.nojekyll
+
# go to the build directory and create a *new* Git repo
cd dist
git init
diff --git a/package.json b/package.json
index 73dd7b1..9a53fcc 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "@the-grid/ed",
"author": "Forrest Oliphant, The Grid",
"license": "MIT",
- "version": "1.4.2",
+ "version": "2.0.0-11",
"description": "the grid api with prosemirror",
"main": "dist/ed.js",
"scripts": {
@@ -31,10 +31,23 @@
"@the-grid/ced": "0.1.3",
"@the-grid/ed-location": "2.0.1",
"@the-grid/ed-userhtml": "0.3.0",
+ "crel": "3.0.0",
"he": "1.1.0",
"imgflo-url": "1.2.0",
"lodash": "4.17.4",
- "prosemirror": "0.10.1",
+ "prosemirror-commands": "0.17.1",
+ "prosemirror-dropcursor": "0.17.2",
+ "prosemirror-example-setup": "0.17.0",
+ "prosemirror-history": "0.17.0",
+ "prosemirror-inputrules": "0.17.0",
+ "prosemirror-keymap": "0.17.0",
+ "prosemirror-menu": "0.17.0",
+ "prosemirror-model": "0.17.0",
+ "prosemirror-schema-basic": "0.17.0",
+ "prosemirror-schema-list": "0.17.0",
+ "prosemirror-state": "0.17.0",
+ "prosemirror-transform": "0.17.0",
+ "prosemirror-view": "0.17.2",
"react": "15.4.2",
"react-dom": "15.4.2",
"rebass": "0.3.3",
diff --git a/src/components/add-cover.js b/src/components/add-cover.js
index 7af3eed..dd775b2 100644
--- a/src/components/add-cover.js
+++ b/src/components/add-cover.js
@@ -2,10 +2,10 @@ import React, {createElement as el} from 'react'
import ButtonOutline from 'rebass/dist/ButtonOutline'
export const buttonStyle =
- { textTransform: 'uppercase'
- , borderRadius: 4
- , padding: '10px 16px'
- , margin: '0.25em 0.5em'
+ { textTransform: 'uppercase',
+ borderRadius: 4,
+ padding: '10px 16px',
+ margin: '0.25em 0.5em',
}
class AddCover extends React.Component {
@@ -29,11 +29,11 @@ class AddCover extends React.Component {
if (hasCover) return null
return el(ButtonOutline
- , { id: 'AddCover'
- , style: buttonStyle
- , onClick: this.boundAddImage
- , rounded: true
- }
+ , { id: 'AddCover',
+ style: buttonStyle,
+ onClick: this.boundAddImage,
+ rounded: true,
+ }
, 'Add Image'
)
}
diff --git a/src/components/add-fold.js b/src/components/add-fold.js
index 42e343b..12ab4bb 100644
--- a/src/components/add-fold.js
+++ b/src/components/add-fold.js
@@ -23,11 +23,11 @@ class AddFold extends React.Component {
if (hasFold) return null
return el(ButtonOutline
- , { id: 'AddFold'
- , style: buttonStyle
- , onClick: this.boundAddFold
- , rounded: true
- }
+ , { id: 'AddFold',
+ style: buttonStyle,
+ onClick: this.boundAddFold,
+ rounded: true,
+ }
, 'Make Full Post'
)
}
diff --git a/src/components/app.js b/src/components/app.js
index 2563796..27085e3 100644
--- a/src/components/app.js
+++ b/src/components/app.js
@@ -6,10 +6,14 @@ import AddCover from './add-cover'
import AddFold from './add-fold'
import Editable from './editable'
import rebassTheme from './rebass-theme'
+import WidgetEdit from './widget-edit'
+import Modal from './modal'
import EdStore from '../store/ed-store'
import {edCommands} from '../menu/ed-menu'
+import {version as PACKAGE_VERSION} from '../../package.json'
+
export default class App extends React.Component {
constructor (props) {
@@ -32,7 +36,6 @@ export default class App extends React.Component {
}
const { initialContent
- , onMount
, onChange
, onShareFile
, onShareUrl
@@ -42,25 +45,43 @@ export default class App extends React.Component {
, onCommandsChanged } = props
this._store = new EdStore(
- { initialContent
- , onMount
- , onChange
- , onShareFile
- , onShareUrl
- , onRequestCoverUpload
- , onDropFiles
- , onDropFileOnBlock
- , onCommandsChanged
+ { initialContent,
+ onChange,
+ onShareFile,
+ onShareUrl,
+ onRequestCoverUpload,
+ onDropFiles,
+ onDropFileOnBlock,
+ onCommandsChanged,
}
)
this.routeChange = this._store.routeChange.bind(this._store)
+
+ this._store.on('media.block.edit.open', (blockID) => {
+ // TODO expose prop for native editors?
+ this.setState({blockToEdit: blockID})
+ this.blur()
+ })
+ this.closeMediaBlockModal = () => {
+ this.setState({blockToEdit: null})
+ }
+ this._store.on('media.block.edit.close', () => {
+ this.closeMediaBlockModal()
+ })
+
+ this.state = {
+ blockToEdit: null,
+ }
}
componentDidMount () {
this.boundOnDragOver = this.onDragOver.bind(this)
window.addEventListener('dragover', this.boundOnDragOver)
this.boundOnDrop = this.onDrop.bind(this)
window.addEventListener('drop', this.boundOnDrop)
+ if (this.props.onMount) {
+ this.props.onMount(this)
+ }
}
componentWillUnmount () {
window.removeEventListener('dragover', this.boundOnDragOver)
@@ -68,19 +89,19 @@ export default class App extends React.Component {
}
getChildContext () {
const {imgfloConfig, featureFlags} = this.props
- return (
- { imgfloConfig
- , featureFlags
- , rebass: rebassTheme
- , store: this._store
- }
- )
+ return ({
+ imgfloConfig,
+ featureFlags,
+ rebass: rebassTheme,
+ store: this._store,
+ })
}
render () {
- return el('div'
- , {className: 'Ed'}
- , this.renderContent()
- , this.renderHints()
+ return el('div',
+ {className: 'Ed'},
+ this.renderContent(),
+ // this.renderHints(),
+ this.renderModal()
)
}
renderContent () {
@@ -94,33 +115,50 @@ export default class App extends React.Component {
, coverPrefs } = this.props
return el('div'
- , { className: 'Ed-Content'
- , style:
- { zIndex: 1
- }
- }
+ , { className: 'Ed-Content',
+ style:
+ { zIndex: 1,
+ },
+ }
, el(Editable
- , { initialContent
- , menuBar
- , onChange: this.routeChange
- , onShareFile
- , onShareUrl
- , onCommandsChanged
- , onDropFiles
- , widgetPath
- , coverPrefs
- }
+ , { initialContent,
+ menuBar,
+ onChange: this.routeChange,
+ onShareFile,
+ onShareUrl,
+ onCommandsChanged,
+ onDropFiles,
+ widgetPath,
+ coverPrefs,
+ }
)
)
}
renderHints () {
return el('div'
- , { className: 'Ed-Hints'
- }
+ , { className: 'Ed-Hints',
+ }
, el(AddCover, {})
, el(AddFold, {})
)
}
+ renderModal () {
+ const {blockToEdit} = this.state
+ if (!blockToEdit) return
+ const initialBlock = this._store.getBlock(blockToEdit)
+ if (!initialBlock) return
+ const {coverPrefs} = this.props
+
+ return el(Modal,
+ {
+ onClose: this.closeMediaBlockModal,
+ child: el(WidgetEdit, {
+ initialBlock,
+ coverPrefs,
+ }),
+ }
+ )
+ }
onDragOver (event) {
// Listening to window
event.preventDefault()
@@ -141,7 +179,8 @@ export default class App extends React.Component {
if (!item) {
throw new Error('commandName not found')
}
- item.spec.run(this._store.pm)
+ const {state, dispatch} = this._store.pm.editor
+ item.spec.run(state, dispatch)
}
insertPlaceholders (index, count) {
return this._store.insertPlaceholders(index, count)
@@ -161,34 +200,42 @@ export default class App extends React.Component {
indexOfFold () {
return this._store.indexOfFold()
}
+ blur () {
+ this.pm.editor.content.blur()
+ window.getSelection().removeAllRanges()
+ }
get pm () {
return this._store.pm
}
-}
-App.childContextTypes =
- { imgfloConfig: React.PropTypes.object
- , store: React.PropTypes.object
- , rebass: React.PropTypes.object
- , featureFlags: React.PropTypes.object
- }
-App.propTypes =
- { initialContent: React.PropTypes.array.isRequired
- , onChange: React.PropTypes.func.isRequired
- , onShareFile: React.PropTypes.func.isRequired
- , onShareUrl: React.PropTypes.func.isRequired
- , onDropFiles: React.PropTypes.func
- , onCommandsChanged: React.PropTypes.func
- , onRequestCoverUpload: React.PropTypes.func.isRequired
- , imgfloConfig: React.PropTypes.object
- , widgetPath: React.PropTypes.string
- , coverPrefs: React.PropTypes.object
- , menuBar: React.PropTypes.bool
- , onMount: React.PropTypes.func
- , onDropFileOnBlock: React.PropTypes.func
- , featureFlags: React.PropTypes.object
- }
-App.defaultProps =
- { widgetPath: './node_modules/'
- , menuBar: true
- , featureFlags: {}
+ get version () {
+ return PACKAGE_VERSION
}
+}
+App.childContextTypes = {
+ imgfloConfig: React.PropTypes.object,
+ store: React.PropTypes.object,
+ rebass: React.PropTypes.object,
+ featureFlags: React.PropTypes.object,
+}
+App.propTypes = {
+ initialContent: React.PropTypes.array.isRequired,
+ onChange: React.PropTypes.func.isRequired,
+ onShareFile: React.PropTypes.func.isRequired,
+ onShareUrl: React.PropTypes.func.isRequired,
+ onDropFiles: React.PropTypes.func,
+ onCommandsChanged: React.PropTypes.func,
+ onRequestCoverUpload: React.PropTypes.func.isRequired,
+ imgfloConfig: React.PropTypes.object,
+ widgetPath: React.PropTypes.string,
+ coverPrefs: React.PropTypes.object,
+ menuBar: React.PropTypes.bool,
+ onMount: React.PropTypes.func,
+ onDropFileOnBlock: React.PropTypes.func,
+ featureFlags: React.PropTypes.object,
+}
+App.defaultProps = {
+ widgetPath: './node_modules/',
+ menuBar: true,
+ coverPrefs: {},
+ featureFlags: {},
+}
diff --git a/src/components/attribution-editor.css b/src/components/attribution-editor.css
index 1eeb4b5..3f3c508 100644
--- a/src/components/attribution-editor.css
+++ b/src/components/attribution-editor.css
@@ -1,3 +1,7 @@
+.AttributionEditor {
+ margin-bottom: -1rem;
+}
+
.AttributionEditor-title textarea {
font-size: 24px !important;
margin-top: 0 !important;
diff --git a/src/components/attribution-editor.js b/src/components/attribution-editor.js
index de81cb1..04d8c7d 100644
--- a/src/components/attribution-editor.js
+++ b/src/components/attribution-editor.js
@@ -25,9 +25,9 @@ class AttributionEditor extends React.Component {
constructor (props) {
super(props)
this.state =
- { block: props.initialBlock
- , showDropIndicator: false
- }
+ { block: props.initialBlock,
+ showDropIndicator: false,
+ }
this.boundOnDragOver = this.onDragOver.bind(this)
this.boundOnDragEnter = this.onDragEnter.bind(this)
@@ -55,33 +55,33 @@ class AttributionEditor extends React.Component {
const menus = renderMenus(type, schema, metadata, cover, this.boundOnChange, this.boundOnMoreClick, this.boundOnUploadRequest, this.boundOnCoverRemove, coverPrefs)
return el('div'
- , { className: `AttributionEditor AttributionEditor-${type}`
- , style: widgetStyle
- , onDragOver: this.boundOnDragOver
- , onDragEnter: this.boundOnDragEnter
- , onDragLeave: this.boundOnDragLeave
- , onDrop: this.boundOnDrop
- }
+ , { className: `AttributionEditor AttributionEditor-${type}`,
+ style: widgetStyle,
+ onDragOver: this.boundOnDragOver,
+ onDragEnter: this.boundOnDragEnter,
+ onDragLeave: this.boundOnDragLeave,
+ onDrop: this.boundOnDrop,
+ }
, this.renderPlay()
, this.renderCover()
, this.renderUnsalvageable()
, this.renderFailed()
, this.renderProgress()
, el('div'
- , { className: 'AttributionEditor-metadata'
- , style:
- { position: 'relative'
- }
- }
+ , { className: 'AttributionEditor-metadata',
+ style:
+ { position: 'relative',
+ },
+ }
, renderFields(schema, metadata, this.boundOnChange, type, html)
, el('div'
- , { className: 'AttributionEditor-links'
- , style:
- { margin: '1em -1em 0'
- , position: 'relative'
- , top: 1
- }
- }
+ , { className: 'AttributionEditor-links',
+ style:
+ { margin: '1em -1em 0',
+ position: 'relative',
+ top: 1,
+ },
+ }
, el(DropdownGroup, {menus})
)
)
@@ -105,7 +105,7 @@ class AttributionEditor extends React.Component {
const {store} = this.context
const preview = store.getCoverPreview(id)
if (!cover && !preview) return
- if (cover.unsalvageable) return
+ if (cover && cover.unsalvageable) return
let src, width, height, title
if (cover) {
src = cover.src
@@ -121,13 +121,13 @@ class AttributionEditor extends React.Component {
if (!src) return
let props = {src, width, height, title}
return el('div'
- , { className: 'AttributionEditor-cover'
- , style:
- { width: '100%'
- , position: 'relative'
- , marginBottom: '1rem'
- }
- }
+ , { className: 'AttributionEditor-cover',
+ style:
+ { width: '100%',
+ position: 'relative',
+ marginBottom: '1rem',
+ },
+ }
, el(Image, props)
)
}
@@ -138,11 +138,11 @@ class AttributionEditor extends React.Component {
let upload = null
if (this.canChangeCover()) {
upload = el(Button
- , { onClick: this.boundOnUploadRequest
- , rounded: true
- , color: 'error'
- , backgroundColor: 'white'
- }
+ , { onClick: this.boundOnUploadRequest,
+ rounded: true,
+ color: 'error',
+ backgroundColor: 'white',
+ }
, 'Upload New Image'
)
}
@@ -163,18 +163,18 @@ class AttributionEditor extends React.Component {
let upload = null
if (this.canChangeCover()) {
upload = el(Button
- , { onClick: this.boundOnUploadRequest
- , rounded: true
- , color: 'error'
- , backgroundColor: 'white'
- }
+ , { onClick: this.boundOnUploadRequest,
+ rounded: true,
+ color: 'error',
+ backgroundColor: 'white',
+ }
, 'Upload Image'
)
}
return el(Message
, {theme: 'error'}
- , 'Upload failed, please try again'
+ , 'Upload failed, please try again.'
, el(Space, {auto: true})
, upload
)
@@ -189,10 +189,10 @@ class AttributionEditor extends React.Component {
const color = (failed === true ? 'error' : 'info')
return el(Progress
- , { value: progress / 100
- , style: {margin: '8px 0'}
- , color
- }
+ , { value: progress / 100,
+ style: {margin: '8px 0'},
+ color,
+ }
)
}
renderPlay () {
@@ -203,20 +203,20 @@ class AttributionEditor extends React.Component {
return el('div'
, { style:
- { textAlign: 'right'
- , position: 'relative'
- , top: '-0.5rem'
- }
- }
+ { textAlign: 'right',
+ position: 'relative',
+ top: '-0.5rem',
+ },
+ }
, el('a'
- , { href: block.metadata.isBasedOnUrl
- , target: '_blank'
- , rel: 'noreferrer noopener'
- , style:
- { textDecoration: 'inherit'
- , textTransform: 'uppercase'
- }
- }
+ , { href: block.metadata.isBasedOnUrl,
+ target: '_blank',
+ rel: 'noreferrer noopener',
+ style:
+ { textDecoration: 'inherit',
+ textTransform: 'uppercase',
+ },
+ }
, type + ' '
, el(PlayIcon)
)
@@ -228,23 +228,23 @@ class AttributionEditor extends React.Component {
return el('div'
, { style:
- { position: 'absolute'
- , display: 'flex'
- , justifyContent: 'center'
- , alignItems: 'center'
- , top: 0
- , left: 0
- , width: '100%'
- , height: '100%'
- , textAlign: 'center'
- , fontSize: 36
- , fontWeight: 600
- , color: '#0088EE'
- , padding: '1rem'
- , backgroundColor: 'rgba(255, 255, 255, 0.9)'
- , border: '12px #0088EE solid'
- }
- }
+ { position: 'absolute',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%',
+ textAlign: 'center',
+ fontSize: 36,
+ fontWeight: 600,
+ color: '#0088EE',
+ padding: '1rem',
+ backgroundColor: 'rgba(255, 255, 255, 0.9)',
+ border: '12px #0088EE solid',
+ },
+ }
, 'Drop to replace this image'
)
}
@@ -301,9 +301,9 @@ class AttributionEditor extends React.Component {
const {store} = this.context
const {id} = this.props
store.routeChange('MEDIA_BLOCK_DROP_FILE'
- , { id
- , file: event.dataTransfer.files[0]
- }
+ , { id,
+ file: event.dataTransfer.files[0],
+ }
)
this.setState({showDropIndicator: false})
}
@@ -317,6 +317,7 @@ class AttributionEditor extends React.Component {
switch (key) {
case 'delete':
store.routeChange('MEDIA_BLOCK_REMOVE', id)
+ store.trigger('media.block.edit.close')
return
case 'isBasedOnUrl':
path = ['metadata', 'isBasedOnUrl']
@@ -351,15 +352,15 @@ class AttributionEditor extends React.Component {
AttributionEditor.contextTypes =
{ store: React.PropTypes.object }
AttributionEditor.childContextTypes =
- { imgfloConfig: React.PropTypes.object
- , rebass: React.PropTypes.object
- , store: React.PropTypes.object
- }
+{ imgfloConfig: React.PropTypes.object,
+ rebass: React.PropTypes.object,
+ store: React.PropTypes.object,
+}
AttributionEditor.propTypes =
- { initialBlock: React.PropTypes.object.isRequired
- , id: React.PropTypes.string.isRequired
- , coverPrefs: React.PropTypes.object.isRequired
- }
+{ initialBlock: React.PropTypes.object.isRequired,
+ id: React.PropTypes.string.isRequired,
+ coverPrefs: React.PropTypes.object.isRequired,
+}
export default React.createFactory(AttributionEditor)
@@ -397,13 +398,13 @@ function renderFields (schema, metadata = {}, onChange, type, html) {
function renderTextField (key, label, value, onChange, placeholder) {
return el(TextareaAutosize
- , { className: `AttributionEditor-${key}`
- , placeholder: placeholder || `Enter ${key}`
- , defaultValue: value
- , key: key
- , onChange: makeChange(['metadata', key], onChange)
- , style: {width: '100%'}
- }
+ , { className: `AttributionEditor-${key}`,
+ placeholder: placeholder || `Enter ${key}`,
+ defaultValue: value,
+ key: key,
+ onChange: makeChange(['metadata', key], onChange),
+ style: {width: '100%'},
+ }
)
}
@@ -440,11 +441,11 @@ function renderMenus (type, schema, metadata = {}, cover, onChange, onMoreClick,
}
menus.push(
el(CreditAdd
- , { schema
- , metadata
- , label: '...'
- , onClick: onMoreClick
- }
+ , { schema,
+ metadata,
+ label: '...',
+ onClick: onMoreClick,
+ }
)
)
return menus
@@ -452,36 +453,36 @@ function renderMenus (type, schema, metadata = {}, cover, onChange, onMoreClick,
function renderCreditEditor (onlyUrl, key, label, item, onChange, path) {
return el(CreditEditor
- , { className: `AttributionEditor-${key}`
- , key: key
- , label: label
- , name: item.name
- , url: item.url
- , avatar: item.avatar
- , path: path || [key]
- , onChange
- , onlyUrl
- }
+ , { className: `AttributionEditor-${key}`,
+ key: key,
+ label: label,
+ name: item.name,
+ url: item.url,
+ avatar: item.avatar,
+ path: path || [key],
+ onChange,
+ onlyUrl,
+ }
)
}
function renderImageEditor (hasCover, allowCoverChange, allowCoverRemove, type, title, coverPrefs = {}, onChange, onUploadRequest, onCoverRemove, siteCoverPrefs) {
const {filter, crop, overlay} = coverPrefs
return el(ImageEditor
- , { hasCover
- , allowCoverChange
- , allowCoverRemove
- , title
- , siteCoverPrefs
- , filter
- , crop
- , overlay
- , onChange
- , onUploadRequest
- , onCoverRemove
- , type
- , name: 'Image'
- , label: 'Image'
- }
+ , { hasCover,
+ allowCoverChange,
+ allowCoverRemove,
+ title,
+ siteCoverPrefs,
+ filter,
+ crop,
+ overlay,
+ onChange,
+ onUploadRequest,
+ onCoverRemove,
+ type,
+ name: 'Image',
+ label: 'Image',
+ }
)
}
diff --git a/src/components/attribution-view.js b/src/components/attribution-view.js
new file mode 100644
index 0000000..893cbeb
--- /dev/null
+++ b/src/components/attribution-view.js
@@ -0,0 +1,81 @@
+import React, {createElement as el} from 'react'
+import imgflo from 'imgflo-url'
+import Avatar from 'rebass/dist/Avatar'
+
+class AttributionView extends React.Component {
+ constructor (props) {
+ super(props)
+ this.renderAuthor = this.renderAuthor.bind(this)
+ }
+ render () {
+ const {metadata} = this.props
+ if (!metadata) return null
+
+ return el('div', {},
+ this.renderAuthors(),
+ this.renderPublisher(),
+ this.renderVia()
+ )
+ }
+ renderAuthors () {
+ const {metadata} = this.props
+ const {author} = metadata
+ if (!author || !author.length) return null
+ return el('span', {},
+ 'by ',
+ author.map(this.renderAuthor),
+ )
+ }
+ renderAuthor (author) {
+ const {name, avatar} = author
+ const {imgfloConfig} = this.context
+ let avatarEl
+ if (avatar && avatar.src) {
+ let {src} = avatar
+ if (imgfloConfig) {
+ const params = {
+ input: src,
+ width: 24,
+ }
+ src = imgflo(imgfloConfig, 'passthrough', params)
+ }
+ avatarEl = el(Avatar,
+ {
+ src,
+ size: 24,
+ style: {verticalAlign: 'middle'},
+ }
+ )
+ }
+ return el('span', {},
+ avatarEl,
+ ' ',
+ (name || 'Credit')
+ )
+ }
+ renderPublisher () {
+ const {metadata} = this.props
+ const {publisher} = metadata
+ if (!publisher || !publisher.name) return null
+ return el('span', {},
+ ' on ',
+ publisher.name
+ )
+ }
+ renderVia () {
+ const {metadata} = this.props
+ const {via} = metadata
+ if (!via || !via.name) return null
+ return el('span', {},
+ ' via ',
+ via.name
+ )
+ }
+}
+AttributionView.propTypes = {
+ metadata: React.PropTypes.object,
+}
+AttributionView.contextTypes =
+ { imgfloConfig: React.PropTypes.object }
+
+export default React.createFactory(AttributionView)
diff --git a/src/components/button-confirm.js b/src/components/button-confirm.js
index a9afda9..8c52d4d 100644
--- a/src/components/button-confirm.js
+++ b/src/components/button-confirm.js
@@ -13,11 +13,11 @@ class ButtonConfirm extends React.Component {
const {open} = this.state
return el(ButtonOutline
- , { children: (open ? confirm : label)
- , onClick: (open ? onClick : this.boundOnConfirm)
- , theme
- , style
- }
+ , { children: (open ? confirm : label),
+ onClick: (open ? onClick : this.boundOnConfirm),
+ theme,
+ style,
+ }
)
}
onConfirm () {
@@ -25,10 +25,10 @@ class ButtonConfirm extends React.Component {
}
}
ButtonConfirm.propTypes =
- { confirm: React.PropTypes.string.isRequired
- , label: React.PropTypes.string.isRequired
- , theme: React.PropTypes.string
- , style: React.PropTypes.object
- , onClick: React.PropTypes.func.isRequired
- }
+{ confirm: React.PropTypes.string.isRequired,
+ label: React.PropTypes.string.isRequired,
+ theme: React.PropTypes.string,
+ style: React.PropTypes.object,
+ onClick: React.PropTypes.func.isRequired,
+}
export default React.createFactory(ButtonConfirm)
diff --git a/src/components/credit-add.js b/src/components/credit-add.js
index cf56257..1ba40fc 100644
--- a/src/components/credit-add.js
+++ b/src/components/credit-add.js
@@ -33,17 +33,20 @@ function makeLinks (schema, metadata = {}, onClick) {
}
function makeLink (key, label, onClick, confirm = false) {
- const Component = (confirm ? NavItemConfirm : NavItem)
- return el(Component
- , { key
- , children: label
- , label
- , confirm: (confirm ? 'Are you sure?' : null)
- , theme: (confirm ? 'warning' : 'primary')
- , style: { display: 'block' }
- , onClick: makeClick(key, onClick)
- }
- )
+ let Component = NavItem
+ let props = {
+ key,
+ children: label,
+ label,
+ style: { display: 'block' },
+ onClick: makeClick(key, onClick),
+ }
+ if (confirm) {
+ Component = NavItemConfirm
+ props.confirm = 'Delete forever now.'
+ props.theme = 'warning'
+ }
+ return el(Component, props)
}
function makeClick (key, onClick) {
diff --git a/src/components/credit-editor.js b/src/components/credit-editor.js
index 10cdf41..f17767f 100644
--- a/src/components/credit-editor.js
+++ b/src/components/credit-editor.js
@@ -11,13 +11,13 @@ import ButtonConfirm from './button-confirm'
export default function CreditEditor (props, context) {
const {name, label, url, avatar, onChange, onlyUrl, path} = props
- return el('div'
- , { style:
- { padding: '1rem'
- , width: 360
- , maxWidth: '100%'
- }
- }
+ return el('div', {
+ style: {
+ padding: '1rem',
+ width: 360,
+ maxWidth: '100%',
+ },
+ }
, renderAvatar(avatar, context.imgfloConfig)
, (onlyUrl ? '' : renderLabel(label))
, (onlyUrl
@@ -34,29 +34,29 @@ function renderAvatar (avatar, imgfloConfig) {
if (!avatar || !avatar.src) return
let {src} = avatar
if (imgfloConfig) {
- const params =
- { input: src
- , width: 72
- }
+ const params = {
+ input: src,
+ width: 72,
+ }
src = imgflo(imgfloConfig, 'passthrough', params)
}
return el(Avatar,
- { key: 'avatar'
- , style: {float: 'right'}
- , src
+ { key: 'avatar',
+ style: {float: 'right'},
+ src,
}
)
}
function renderRemove (onChange, path) {
return el(ButtonConfirm
- , { onClick: makeRemove(onChange, path)
- , style: {float: 'right'}
- , theme: 'warning'
- , title: 'delete attribution from block'
- , label: 'Remove'
- , confirm: 'Remove: Are you sure?'
- }
+ , { onClick: makeRemove(onChange, path),
+ style: {float: 'right'},
+ theme: 'warning',
+ title: 'delete attribution from block',
+ label: 'Remove',
+ confirm: 'Remove: Are you sure?',
+ }
)
}
@@ -69,9 +69,9 @@ function renderLabel (label) {
function renderFields (name, url, avatar, onChange, path) {
return (
- [ renderTextField('name', 'Name', name, onChange, path.concat(['name']), true)
- , renderTextField('url', 'Link', url, onChange, path.concat(['url']), false, isUrlOrBlank, 'https...')
- ]
+ [ renderTextField('name', 'Name', name, onChange, path.concat(['name']), true),
+ renderTextField('url', 'Link', url, onChange, path.concat(['url']), false, isUrlOrBlank, 'https...'),
+ ]
)
}
@@ -81,18 +81,18 @@ function renderBasedOnUrl (value, onChange, path) {
function renderTextField (key, label, value, onChange, path, defaultFocus, validator, placeholder) {
return el(TextareaAutosize
- , { className: `AttributionEditor-${key}`
- , label
- , defaultValue: value
- , defaultFocus
- , key: key
- , name: key
- , multiLine: true
- , style: {width: '100%'}
- , onChange: makeChange(onChange, path)
- , validator
- , placeholder
- }
+ , { className: `AttributionEditor-${key}`,
+ label,
+ defaultValue: value,
+ defaultFocus,
+ key: key,
+ name: key,
+ multiLine: true,
+ style: {width: '100%'},
+ onChange: makeChange(onChange, path),
+ validator,
+ placeholder,
+ }
)
}
diff --git a/src/components/dropdown-group.js b/src/components/dropdown-group.js
index b5bd8dd..a68fdca 100644
--- a/src/components/dropdown-group.js
+++ b/src/components/dropdown-group.js
@@ -1,3 +1,5 @@
+/* eslint react/no-find-dom-node: [0] */
+
import React, {createElement as el} from 'react'
import Menu from 'rebass/dist/Menu'
@@ -18,20 +20,23 @@ class DropdownGroup extends React.Component {
constructor (props) {
super(props)
this.state = {
- openMenu: null
+ openMenu: null,
}
this.boundCloseMenu = this.closeMenu.bind(this)
this.nodeRefCallback = (node) => { this.node = node }
+ this.onKeyDown = (event) => {
+ if (event.key === 'Enter') {
+ event.preventDefault()
+ event.stopPropagation()
+ this.setState({openMenu: null})
+ }
+ }
}
componentDidMount () {
// Click away to dismiss
- const el = document.querySelector('.ProseMirror-content')
- el.addEventListener('focus', this.boundCloseMenu)
document.body.addEventListener('click', this.boundCloseMenu)
}
componentWillUnmount () {
- const el = document.querySelector('.ProseMirror-content')
- el.removeEventListener('focus', this.boundCloseMenu)
document.body.removeEventListener('click', this.boundCloseMenu)
}
componentWillReceiveProps (nextProps) {
@@ -64,12 +69,14 @@ class DropdownGroup extends React.Component {
}
}
render () {
- return el('div'
- , { className: 'DropdownGroup'
- , ref: this.nodeRefCallback
- }
- , this.renderButtons()
- , this.renderMenu()
+ return el('div',
+ {
+ className: 'DropdownGroup',
+ ref: this.nodeRefCallback,
+ onKeyDown: this.onKeyDown,
+ },
+ this.renderButtons(),
+ this.renderMenu()
)
}
renderButtons () {
@@ -81,32 +88,31 @@ class DropdownGroup extends React.Component {
// HACK
const {name, label} = menus[i].props
buttons.push(
- el(ButtonOutline
- , { key: `button${i}`
- , onClick: this.makeOpenMenu(i)
- , theme: (openMenu === i ? 'primary' : theme)
- , inverted: false
- , style:
- { borderWidth: 0
- , boxShadow: 'none'
- , outline: 'none'
- }
- , rounded: false
- , title: `Edit ${label}`
- }
- , el('span'
- , { style:
- { maxWidth: '15rem'
- , verticalAlign: 'middle'
- , display: 'inline-block'
- , whiteSpace: 'pre'
- , overflow: 'hidden'
- , textOverflow: 'ellipsis'
- , textTransform: 'uppercase'
- }
- }
- , (name || label)
- )
+ el(ButtonOutline, {
+ key: `button${i}`,
+ onClick: this.makeOpenMenu(i),
+ theme: (openMenu === i ? 'primary' : theme),
+ inverted: false,
+ style: {
+ borderWidth: 0,
+ boxShadow: 'none',
+ outline: 'none',
+ },
+ rounded: false,
+ title: `Edit ${label}`,
+ },
+ el('span', {
+ style: {
+ maxWidth: '15rem',
+ verticalAlign: 'middle',
+ display: 'inline-block',
+ whiteSpace: 'pre',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ textTransform: 'uppercase',
+ }},
+ (name || label)
+ )
)
)
}
@@ -118,21 +124,18 @@ class DropdownGroup extends React.Component {
if (openMenu == null) return
- return el('div'
- , { style:
- { position: 'relative' }
- }
- , el(Menu
- , { theme
- , style:
- { textAlign: 'left'
- , position: 'absolute'
- , top: -1
- , right: -1
- , zIndex: 100
- }
- }
- , menus[openMenu]
+ return el('div', {style: { position: 'relative' }},
+ el(Menu, {
+ theme,
+ style: {
+ textAlign: 'left',
+ position: 'absolute',
+ top: -1,
+ right: -1,
+ zIndex: 100,
+ marginBottom: '5rem',
+ },
+ }, menus[openMenu]
)
)
}
@@ -157,9 +160,9 @@ class DropdownGroup extends React.Component {
}
}
DropdownGroup.propTypes =
- { menus: React.PropTypes.array.isRequired
- , theme: React.PropTypes.string
- }
+{ menus: React.PropTypes.array.isRequired,
+ theme: React.PropTypes.string,
+}
DropdownGroup.defaultProps =
{ theme: 'secondary' }
export default React.createFactory(DropdownGroup)
diff --git a/src/components/editable-feature-flags.css b/src/components/editable-feature-flags.css
index bc5791d..bcd12a5 100644
--- a/src/components/editable-feature-flags.css
+++ b/src/components/editable-feature-flags.css
@@ -1,4 +1,5 @@
.FlaggedWidget > div {
+ position: relative;
}
.FlaggedWidget > div::after {
content: 'Paid feature: will not show on site until upgrade.';
@@ -10,6 +11,7 @@
position: absolute;
border: 2px solid red;
padding: 2px 4px;
+ border-radius: 0 2px 0 0;
}
.ProseMirror-menu-dropdown-item .flaggedFeature {
diff --git a/src/components/editable.css b/src/components/editable.css
index d15b7c5..46d7d9c 100644
--- a/src/components/editable.css
+++ b/src/components/editable.css
@@ -65,31 +65,22 @@
.ProseMirror-content h1 {
font-size: 250%;
- line-height: 1.2 !important;
+ line-height: 1.2;
}
.ProseMirror-content h2 {
font-size: 175%;
- line-height: 1.3 !important;
+ line-height: 1.3;
}
.ProseMirror-content h3 {
font-size: 125%;
- line-height: 1.4 !important;
+ line-height: 1.4;
}
-.ProseMirror-content h1 * {
- line-height: 1.2 !important;
-}
-.ProseMirror-content h2 * {
- line-height: 1.3 !important;
-}
-.ProseMirror-content h3 * {
- line-height: 1.4 !important;
-}
-
-.ProseMirror-content p * {
- font-size: 20px !important;
+.ProseMirror-content p {
+ font-size: 100%;
+ line-height: 1.5;
}
.ProseMirror-content p:first-child, .ProseMirror-content h1:first-child, .ProseMirror-content h2:first-child, .ProseMirror-content h3:first-child, .ProseMirror-content h4:first-child, .ProseMirror-content h5:first-child, .ProseMirror-content h6:first-child {
@@ -119,11 +110,21 @@
}
.ProseMirror-prompt {
- padding: 1em;
+ background-color: white;
+ border: 1px silver solid;
+
+ position: absolute;
+ padding: 0.5em;
+}
+
+.ProseMirror-prompt input {
+ padding: .5em;
+ display: block;
+ margin-bottom: 0.5em;
}
-.ProseMirror-prompt input[type='text']{
- padding: .5em 1em;
+.ProseMirror-prompt h5 {
+ margin: 0 0 0.5em 0;
}
.ProseMirror-prompt .ProseMirror-prompt-close {
@@ -133,6 +134,9 @@
.ProseMirror-content hr {
border-width: 2px 0 0 0;
+ /* Make it easier to tap */
+ padding-bottom: 1rem;
+ margin-bottom: 0.75rem;
}
/* Placeholder text hacks */
@@ -143,13 +147,25 @@
content: 'Title';
opacity: 0.2;
position: absolute;
- bottom: 0;
+ top: 0;
+}
+.ProseMirror-content > h2.empty::after {
+ content: 'Section';
+ opacity: 0.2;
+ position: absolute;
+ top: 0;
+}
+.ProseMirror-content > h3.empty::after {
+ content: 'Subsection';
+ opacity: 0.2;
+ position: absolute;
+ top: 0;
}
.ProseMirror-content > p.empty::after {
content: '¶';
opacity: 0.2;
position: absolute;
- bottom: 0;
+ top: 0;
}
diff --git a/src/components/editable.js b/src/components/editable.js
index 778daee..371c0ae 100644
--- a/src/components/editable.js
+++ b/src/components/editable.js
@@ -1,26 +1,30 @@
+require('prosemirror-menu/style/menu.css')
+require('prosemirror-view/style/prosemirror.css')
require('./editable.css')
require('./editable-menu.css')
require('./editable-feature-flags.css')
import React, {createElement as el} from 'react'
-import {ProseMirror} from 'prosemirror/dist/edit/main'
-import {Plugin} from 'prosemirror/dist/edit/plugin'
+import {EditorState, Plugin, NodeSelection} from 'prosemirror-state'
+import {history as pluginHistory} from 'prosemirror-history'
+// import {dropCursor as pluginDropCursor} from 'prosemirror-dropcursor'
-import {menuBar as pluginMenuBar} from 'prosemirror/dist/menu'
-import {patchMenuWithFeatureFlags} from '../menu/ed-menu'
+import {MenuBarEditorView} from 'prosemirror-menu'
+import {edMenuPlugin, edMenuEmptyPlugin, patchMenuWithFeatureFlags} from '../menu/ed-menu'
import GridToDoc from '../convert/grid-to-doc'
-import EdKeymap from '../inputrules/ed-keymap'
-import EdSchemaFull from '../schema/ed-schema-full'
-import EdInputRules from '../inputrules/ed-input-rules'
+import EdSchema from '../schema/ed-schema'
+import {MediaNodeView} from '../schema/media'
+import {edInputRules, edBaseKeymap, edKeymap} from '../inputrules/ed-input-rules'
import {posToIndex} from '../util/pm'
+import {isDropFileEvent} from '../util/drop'
-import PluginWidget from '../plugins/widget.js'
import PluginShareUrl from '../plugins/share-url'
-import PluginContentHints from '../plugins/content-hints'
+// import PluginContentHints from '../plugins/content-hints'
import PluginPlaceholder from '../plugins/placeholder'
import PluginFixedMenuHack from '../plugins/fixed-menu-hack'
import PluginCommandsInterface from '../plugins/commands-interface'
+import {PluginStoreRef} from '../plugins/store-ref'
class Editable extends React.Component {
@@ -32,14 +36,11 @@ class Editable extends React.Component {
throw new Error('Can not setState of Editable')
}
render () {
- return el('div'
- , { className: 'Editable'
- , style:
- { position: 'relative' /* So widgets can position selves */
- }
- }
- , el('div', {className: 'Editable-Mirror', ref: 'mirror'})
- , el('div', {className: 'Editable-Plugins', ref: 'plugins'})
+ return el('div', {
+ className: 'Editable',
+ },
+ el('div', {className: 'Editable-Mirror', ref: 'mirror'}),
+ el('div', {className: 'Editable-Plugins', ref: 'plugins'})
)
}
componentDidMount () {
@@ -50,103 +51,129 @@ class Editable extends React.Component {
, onCommandsChanged
, widgetPath
, coverPrefs } = this.props
- const {store, featureFlags} = this.context
-
- // PM setup
- let pmOptions =
- { place: mirror
- , autoInput: true
- // , commands: commands
- , doc: GridToDoc(initialContent)
- , schema: EdSchemaFull
- , plugins: [ EdInputRules ]
- }
-
- let edPluginClasses =
- [ PluginWidget
- , PluginShareUrl
- , PluginContentHints
- , PluginPlaceholder
- ]
-
+ const {store, imgfloConfig, featureFlags} = this.context
+
+ let edPlugins = [
+ pluginHistory(),
+ edInputRules,
+ edKeymap,
+ edBaseKeymap,
+ ]
+
+ let edPluginClasses = [
+ PluginStoreRef,
+ PluginShareUrl,
+ PluginPlaceholder,
+ ]
+
+ patchMenuWithFeatureFlags(featureFlags)
if (menuBar) {
- const menuContent = patchMenuWithFeatureFlags(featureFlags)
- let menu = pluginMenuBar.config(
- { float: false
- , content: menuContent
- }
- )
- pmOptions.plugins.push(menu)
+ edPlugins.push(edMenuPlugin)
edPluginClasses.push(PluginFixedMenuHack)
+ } else {
+ edPlugins.push(edMenuEmptyPlugin)
}
if (onCommandsChanged) {
edPluginClasses.push(PluginCommandsInterface)
}
- const pluginOptions =
- { ed: store
- , editableView: this
- , container: plugins
- , widgetPath
- , featureFlags
- , coverPrefs
- }
+ const pluginProps = {
+ ed: store,
+ editableView: this,
+ elMirror: mirror,
+ elPlugins: plugins,
+ widgetPath,
+ coverPrefs,
+ }
edPluginClasses.forEach(function (plugin) {
- const p = new Plugin(plugin, pluginOptions)
- pmOptions.plugins.push(p)
+ // FIXME least knowledge per plugin
+ plugin.edStuff = pluginProps
+ const p = new Plugin(plugin)
+ edPlugins.push(p)
})
- this.pm = new ProseMirror(pmOptions)
- this.pm.ed = store
-
- this.pm.on.change.add(() => {
- onChange('EDITABLE_CHANGE', this.pm)
+ const state = EditorState.create({
+ schema: EdSchema,
+ doc: GridToDoc(initialContent),
+ plugins: edPlugins,
+ ed: store,
})
- this.pm.on.domDrop.add(this.boundOnDrop)
+ let view
- this.pm.addKeymap(EdKeymap)
+ const applyTransaction = (transaction) => {
+ view.updateState(view.editor.state.apply(transaction))
+ if (transaction.steps.length) {
+ onChange('EDITABLE_CHANGE', this.pm)
+ }
+ }
+
+ // PM setup
+ let pmOptions =
+ { state,
+ autoInput: true,
+ spellcheck: true,
+ dispatchTransaction: applyTransaction,
+ handleClickOn: function (_view, _pos, node) { return node.type.name === 'media' },
+ nodeViews: {
+ media: (node, view, getPos) => {
+ return new MediaNodeView(node, view, getPos, store, imgfloConfig, coverPrefs, widgetPath, featureFlags)
+ },
+ },
+ editable: function (state) { return true },
+ attributes: { class: 'ProseMirror-content' },
+ handleDOMEvents: {
+ drop: this.boundOnDrop,
+ },
+ // Don't type over node selection
+ handleTextInput: function (view, from, to, text) {
+ if (view.state.selection instanceof NodeSelection) {
+ return true
+ }
+ },
+ }
+ view = this.pm = new MenuBarEditorView(mirror, pmOptions)
+ this.pm.ed = store
onChange('EDITABLE_INITIALIZE', this)
}
componentWillUnmount () {
- // this.pm.off('change')
- // this.pm.off('ed.plugin.url')
- // this.pm.off('ed.menu.file')
- // this.pm.off('drop', this.boundOnDrop)
- const pluginKeys = Object.keys(this.pm.plugin)
- pluginKeys.forEach((key) => this.pm.plugin[key].detach())
+ this.pm.editor.destroy()
}
- onDrop (event) {
- if (!event.dataTransfer || !event.dataTransfer.files || !event.dataTransfer.files.length) return
+ onDrop (editor, event) {
+ if (!isDropFileEvent(event)) return
const {onDropFiles} = this.props
if (!onDropFiles) return
- const pos = this.pm.posAtCoords({left: event.clientX, top: event.clientY})
+ const {pos} = this.pm.editor.posAtCoords({left: event.clientX, top: event.clientY})
if (pos == null) return
- const index = posToIndex(this.pm.doc, pos)
+ const index = posToIndex(editor.state.doc, pos)
if (index == null) return
event.preventDefault()
event.stopPropagation()
onDropFiles(index, event.dataTransfer.files)
}
}
-Editable.contextTypes =
- { store: React.PropTypes.object
- , featureFlags: React.PropTypes.object
- }
-Editable.propTypes =
- { initialContent: React.PropTypes.array.isRequired
- , menuBar: React.PropTypes.bool
- , onChange: React.PropTypes.func.isRequired
- , onShareFile: React.PropTypes.func
- , onShareUrl: React.PropTypes.func
- , onDropFiles: React.PropTypes.func
- , onEditableInit: React.PropTypes.func
- , onCommandsChanged: React.PropTypes.func
- , widgetPath: React.PropTypes.string
- , coverPrefs: React.PropTypes.object
- }
+Editable.contextTypes = {
+ store: React.PropTypes.object,
+ imgfloConfig: React.PropTypes.object,
+ featureFlags: React.PropTypes.object,
+}
+Editable.propTypes = {
+ initialContent: React.PropTypes.array.isRequired,
+ menuBar: React.PropTypes.bool,
+ onChange: React.PropTypes.func.isRequired,
+ onShareFile: React.PropTypes.func,
+ onShareUrl: React.PropTypes.func,
+ onDropFiles: React.PropTypes.func,
+ onEditableInit: React.PropTypes.func,
+ onCommandsChanged: React.PropTypes.func,
+ widgetPath: React.PropTypes.string,
+ coverPrefs: React.PropTypes.object,
+}
+Editable.defaultProps = {
+ coverPrefs: {},
+}
export default React.createFactory(Editable)
diff --git a/src/components/icons.js b/src/components/icons.js
index 7594f8c..cdb6fb2 100644
--- a/src/components/icons.js
+++ b/src/components/icons.js
@@ -2,37 +2,42 @@
import React, {createElement as el} from 'react'
+// paths from https://github.com/jxnblk/geomicons-open/blob/master/src/play.js
+const PATHS = {
+ play: 'M4 4 L28 16 L4 28 z',
+ link: 'M0 16 A8 8 0 0 1 8 8 L14 8 A8 8 0 0 1 22 16 L18 16 A4 4 0 0 0 14 12 L8 12 A4 4 0 0 0 4 16 A4 4 0 0 0 8 20 L10 24 L8 24 A8 8 0 0 1 0 16z M22 8 L24 8 A8 8 0 0 1 32 16 A8 8 0 0 1 24 24 L18 24 A8 8 0 0 1 10 16 L14 16 A4 4 0 0 0 18 20 L24 20 A4 4 0 0 0 28 16 A4 4 0 0 0 24 12z',
+}
+
-// path from https://github.com/jxnblk/geomicons-open/blob/master/src/play.js
-export function Play (props) {
- const {fill, width, height} = props
+export default function Icon (props) {
+ const {fill, width, height, icon} = props
return el('svg'
- , { viewBox: '0 0 32 32'
- , fill
- , width
- , height
- }
+ , { viewBox: '0 0 32 32',
+ fill,
+ width,
+ height,
+ style: {verticalAlign: 'middle'},
+ }
, el('path'
- , { d: 'M4 4 L28 16 L4 28 z'
- }
+ , { d: PATHS[icon],
+ }
)
)
}
-Play.defaultProps =
- { fill: 'currentColor'
- , width: '0.9em'
- , height: '0.9em'
- }
-Play.propTypes =
- { fill: React.PropTypes.string
- , width: React.PropTypes.oneOfType(
- [ React.PropTypes.string
- , React.PropTypes.number
- ]
- )
- , height: React.PropTypes.oneOfType(
- [ React.PropTypes.string
- , React.PropTypes.number
- ]
- )
- }
+Icon.defaultProps = {
+ fill: 'currentColor',
+ width: '1em',
+ height: '1em',
+}
+Icon.propTypes = {
+ icon: React.PropTypes.string.isRequired,
+ fill: React.PropTypes.string,
+ width: React.PropTypes.oneOfType([
+ React.PropTypes.string,
+ React.PropTypes.number,
+ ]),
+ height: React.PropTypes.oneOfType([
+ React.PropTypes.string,
+ React.PropTypes.number,
+ ]),
+}
diff --git a/src/components/image-editor.js b/src/components/image-editor.js
index d8c943b..2069cd8 100644
--- a/src/components/image-editor.js
+++ b/src/components/image-editor.js
@@ -15,7 +15,7 @@ export default function ImageEditor (props, context) {
, overlay
, onChange
, onUploadRequest
- , onCoverRemove
+ , onCoverRemove,
} = props
let toggles = null
@@ -30,11 +30,11 @@ export default function ImageEditor (props, context) {
return el('div'
, { style:
- { padding: '1rem'
- , width: 288
- , maxWidth: '100%'
- }
- }
+ { padding: '1rem',
+ width: 288,
+ maxWidth: '100%',
+ },
+ }
, toggles
, (allowCoverChange ? renderUploadButton(onUploadRequest) : null)
, (allowCoverRemove ? renderRemoveButton(onCoverRemove) : null)
@@ -44,15 +44,15 @@ export default function ImageEditor (props, context) {
function renderToggle (key, label, value, onChange, path, siteAllow) {
const readOnly = (siteAllow === false)
return el(Checkbox
- , { key
- , label: label + (readOnly ? ' (off site-wide)' : '')
- , name: key
- , checked: (siteAllow !== false && value !== false)
- , style: (readOnly ? {opacity: 0.5} : {})
- , readOnly
- , disabled: readOnly
- , onChange: makeChange(path, onChange, true)
- }
+ , { key,
+ label: label + (readOnly ? ' (off site-wide)' : ''),
+ name: key,
+ checked: (siteAllow !== false && value !== false),
+ style: (readOnly ? {opacity: 0.5} : {}),
+ readOnly,
+ disabled: readOnly,
+ onChange: makeChange(path, onChange, true),
+ }
)
}
@@ -65,24 +65,24 @@ function makeChange (path, onChange, checked = false) {
function renderUploadButton (onClick) {
return el(ButtonOutline
- , { onClick
- , theme: 'warning'
- , style: { width: '100%' }
- }
+ , { onClick,
+ theme: 'warning',
+ style: { width: '100%' },
+ }
, 'Upload New Image'
)
}
function renderRemoveButton (onClick) {
return el(ButtonConfirm
- , { onClick
- , label: 'Remove Image'
- , confirm: 'Remove Image: Are you sure?'
- , theme: 'warning'
- , style:
- { width: '100%'
- , marginTop: '0.5rem'
- }
- }
+ , { onClick,
+ label: 'Remove Image',
+ confirm: 'Remove Image: Are you sure?',
+ theme: 'warning',
+ style:
+ { width: '100%',
+ marginTop: '0.5rem',
+ },
+ }
)
}
diff --git a/src/components/image.js b/src/components/image.js
index 4901c22..c7abaaa 100644
--- a/src/components/image.js
+++ b/src/components/image.js
@@ -14,8 +14,8 @@ export default function Image (props, context) {
const {width, height} = props
if (context && context.imgfloConfig) {
const params =
- { input: src
- , width: getSize(width, height)
+ { input: src,
+ width: getSize(width, height),
}
src = imgflo(context.imgfloConfig, 'passthrough', params)
}
@@ -24,21 +24,18 @@ export default function Image (props, context) {
)
}
Image.contextTypes = {
- imgfloConfig: React.PropTypes.object
+ imgfloConfig: React.PropTypes.object,
}
-// Proxy via imgflo with width multiple of 360
+// Proxy via imgflo with width multiple of 72
function getSize (width, height) {
- let size = width || 360
- if (width && (width >= 360)) {
- size = 360
+ let size = width || 216
+ if (width && (width >= 216)) {
+ size = 216
}
- if (width && (width >= 720)) {
- size = 720
- }
- if (height && height > width && (width >= 360)) {
- size = 360
+ if (height && height > width && (width >= 144)) {
+ size = 144
}
return size
}
diff --git a/src/components/media.js b/src/components/media.js
deleted file mode 100644
index b59ef59..0000000
--- a/src/components/media.js
+++ /dev/null
@@ -1,97 +0,0 @@
-import React, {createElement as el} from 'react'
-import _ from '../util/lodash'
-
-import Placeholder from './placeholder'
-import AttributionEditor from './attribution-editor'
-import WidgetCta from './widget-cta'
-import WidgetUnsupported from './widget-unsupported'
-import rebassTheme from './rebass-theme'
-
-const Components =
- { placeholder: Placeholder
- , cta: WidgetCta
- , image: AttributionEditor
- , video: AttributionEditor
- , article: AttributionEditor
- , interactive: AttributionEditor
- , quote: AttributionEditor
- , unsupported: WidgetUnsupported
- }
-
-
-class Media extends React.Component {
- constructor (props) {
- super(props)
-
- const {initialBlock} = props
- const {id} = initialBlock
- this.state = {id, initialBlock}
-
- const {store} = props
- this.boundUpdateBlock = this.updateBlock.bind(this)
- this.boundUpdateBlockAll = this.updateBlockAll.bind(this)
- store.on('media.update.id', this.boundUpdateBlock)
- store.on('media.update', this.boundUpdateBlockAll)
- }
- componentWillUnmount () {
- const {store} = this.props
- store.off('media.update.id', this.boundUpdateBlock)
- store.off('media.update', this.boundUpdateBlockAll)
- }
- getChildContext () {
- return (
- { imgfloConfig: (this.context.imgfloConfig || this.props.imgfloConfig)
- , store: (this.context.store || this.props.store)
- , rebass: rebassTheme
- }
- )
- }
- render () {
- const {coverPrefs} = this.props
- const {initialBlock, id} = this.state
- const {type} = initialBlock
- let Component = Components[type] || Components.unsupported
- return el(Component
- , { initialBlock
- , id
- , coverPrefs
- }
- )
- }
- updateBlock (updateId) {
- const {id} = this.state
- if (id !== updateId) {
- return
- }
- const {store} = this.props
- const initialBlock = store.getBlock(id)
- if (!initialBlock) return
- this.setState({initialBlock})
- }
- updateBlockAll () {
- const {store} = this.props
- const {id, initialBlock} = this.state
- const block = store.getBlock(id)
- if (!block) return
- // Needed to speed up ed.setContent
- // and not render every block every time
- if (_.isEqual(initialBlock, block)) return
- this.setState({initialBlock: block})
- }
-}
-Media.contextTypes =
- { imgfloConfig: React.PropTypes.object
- , store: React.PropTypes.object
- }
-Media.childContextTypes =
- { imgfloConfig: React.PropTypes.object
- , rebass: React.PropTypes.object
- , store: React.PropTypes.object
- }
-Media.propTypes =
- { initialBlock: React.PropTypes.object.isRequired
- , store: React.PropTypes.object.isRequired
- , coverPrefs: React.PropTypes.object.isRequired
- , imgfloConfig: React.PropTypes.object
- }
-export default React.createFactory(Media)
diff --git a/src/components/modal.js b/src/components/modal.js
new file mode 100644
index 0000000..c0a334d
--- /dev/null
+++ b/src/components/modal.js
@@ -0,0 +1,71 @@
+import React, {createElement as el} from 'react'
+
+import ButtonOutline from 'rebass/dist/ButtonOutline'
+import {pseudoFixedStyle} from '../util/browser'
+function stopPropagation (event) { event.stopPropagation() }
+
+
+class Modal extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.onKeyDown = (event) => {
+ if (event.key === 'Enter') {
+ event.preventDefault()
+ event.stopPropagation()
+ props.onClose()
+ }
+ }
+ }
+ render () {
+ const {onClose, child} = this.props
+
+ let bgStyle = pseudoFixedStyle()
+ bgStyle.backgroundColor = 'rgba(128,128,128,0.8)'
+ bgStyle.zIndex = 4
+ bgStyle.overflowY = 'auto'
+
+ return el('div',
+ {
+ className: 'Modal-bg',
+ style: bgStyle,
+ onClick: onClose,
+ onKeyDown: this.onKeyDown,
+ },
+ el('div',
+ {
+ className: 'Modal-container',
+ style: {
+ padding: '1rem',
+ backgroundColor: 'white',
+ maxWidth: 720,
+ margin: '1rem auto 4rem',
+ border: '1px solid silver',
+ borderRadius: 2,
+ },
+ onClick: stopPropagation,
+ },
+ el('div',
+ {
+ style: {
+ textAlign: 'right',
+ marginBottom: '1rem',
+ },
+ },
+ el(ButtonOutline,
+ {
+ onClick: onClose,
+ },
+ 'Close'
+ ),
+ ),
+ child
+ )
+ )
+ }
+}
+Modal.propTypes = {
+ onClose: React.PropTypes.func,
+ child: React.PropTypes.node,
+}
+export default React.createFactory(Modal)
diff --git a/src/components/nav-item-confirm.js b/src/components/nav-item-confirm.js
index cc2e58a..5073aff 100644
--- a/src/components/nav-item-confirm.js
+++ b/src/components/nav-item-confirm.js
@@ -12,23 +12,22 @@ class NavItemConfirm extends React.Component {
const {confirm, label, theme, style, onClick} = this.props
const {open} = this.state
- return el(NavItem
- , { children: (open ? confirm : label)
- , onClick: (open ? onClick : this.boundOnConfirm)
- , theme
- , style
- }
- )
+ return el(NavItem, {
+ children: (open ? confirm : label),
+ onClick: (open ? onClick : this.boundOnConfirm),
+ theme,
+ style,
+ })
}
onConfirm () {
this.setState({open: true})
}
}
NavItemConfirm.propTypes =
- { confirm: React.PropTypes.string.isRequired
- , label: React.PropTypes.string.isRequired
- , theme: React.PropTypes.string
- , style: React.PropTypes.object
- , onClick: React.PropTypes.func.isRequired
- }
+{ confirm: React.PropTypes.string.isRequired,
+ label: React.PropTypes.string.isRequired,
+ theme: React.PropTypes.string,
+ style: React.PropTypes.object,
+ onClick: React.PropTypes.func.isRequired,
+}
export default React.createFactory(NavItemConfirm)
diff --git a/src/components/placeholder.js b/src/components/placeholder.js
index d002736..072581f 100644
--- a/src/components/placeholder.js
+++ b/src/components/placeholder.js
@@ -17,12 +17,12 @@ export default function Placeholder (props, context) {
const theme = (failed === true ? 'error' : 'info')
return el('div'
- , { className: `Placeholder Placeholder-${theme}`
- }
+ , { className: `Placeholder Placeholder-${theme}`,
+ }
, el(Message
- , { theme
- , style: {marginBottom: 0}
- }
+ , { theme,
+ style: {marginBottom: 0},
+ }
, el('span', {className: 'Placeholder-status'}, status)
, makePreview(id, store)
, el(Space
@@ -43,13 +43,13 @@ function makePreview (id, store) {
if (!preview) return
return el('div'
, { style:
- { width: 96
- , height: 72
- , display: 'inline-block'
- , margin: '0px 16px'
- , overflow: 'hidden'
- }
- }
+ { width: 96,
+ height: 72,
+ display: 'inline-block',
+ margin: '0px 16px',
+ overflow: 'hidden',
+ },
+ }
, el(Image, {src: preview})
)
}
@@ -57,10 +57,10 @@ function makePreview (id, store) {
function makeProgress (progress, color) {
if (progress == null) return
return el(Progress
- , { value: progress / 100
- , style: {marginTop: 16}
- , color
- }
+ , { value: progress / 100,
+ style: {marginTop: 16},
+ color,
+ }
)
}
diff --git a/src/components/rebass-theme.js b/src/components/rebass-theme.js
index 04031e2..1168cc8 100644
--- a/src/components/rebass-theme.js
+++ b/src/components/rebass-theme.js
@@ -5,43 +5,30 @@ export const sans = '-apple-system, ".SFNSText-Regular", "San Francisco", "Robot
export const colors = rebassDefaults.colors
-export const widgetStyle =
- { padding: '1rem 1rem 0'
- , background: '#fff'
- , border: '1px solid #ddd'
- , borderRadius: 2
- , position: 'relative'
- }
-
-export const widgetLeftStyle =
- { paddingLeft: '1rem'
- , borderLeft: '1px solid #ddd'
- , background: '#fff'
- }
const theme =
- { name: 'Ed Theme'
- , fontFamily: sans
- , colors: rebassDefaults.colors
- , Base:
- { fontFamily: sans
- }
- , Button:
- { fontFamily: sans
- }
- , ButtonOutline:
- { fontFamily: sans
- , boxShadow: 'inset 0 0 0 1px #ddd'
- }
- , NavItem:
- { fontFamily: sans
- }
- , Panel:
- { fontFamily: sans
- }
- , Message:
- { fontFamily: sans
- }
+ { name: 'Ed Theme',
+ fontFamily: sans,
+ colors: rebassDefaults.colors,
+ Base:
+ { fontFamily: sans,
+ },
+ Button:
+ { fontFamily: sans,
+ },
+ ButtonOutline:
+ { fontFamily: sans,
+ boxShadow: 'inset 0 0 0 1px #ddd',
+ },
+ NavItem:
+ { fontFamily: sans,
+ },
+ Panel:
+ { fontFamily: sans,
+ },
+ Message:
+ { fontFamily: sans,
+ },
}
export default theme
diff --git a/src/components/textarea-autosize.js b/src/components/textarea-autosize.js
index bdcc45e..b3b5151 100644
--- a/src/components/textarea-autosize.js
+++ b/src/components/textarea-autosize.js
@@ -4,30 +4,30 @@ import React, {createElement as el} from 'react'
import {sans, colors} from './rebass-theme'
const containerStyle =
- { fontFamily: sans
- , fontSize: 12
+ { fontFamily: sans,
+ fontSize: 12,
}
const labelStyle = {}
const labelStyleError =
- { color: colors.error
+ { color: colors.error,
}
const areaStyle =
- { fontFamily: sans
- , minHeight: '1.5rem'
- , display: 'block'
- , width: '100%'
- , padding: 0
- , resize: 'none'
- , color: 'inherit'
- , border: 0
- , borderBottom: '1px dotted rgba(0, 136, 238, .2)'
- , borderRadius: 0
- , outline: 'none'
- , overflow: 'hidden'
- , marginBottom: '0.75rem'
+ { fontFamily: sans,
+ minHeight: '1.5rem',
+ display: 'block',
+ width: '100%',
+ padding: 0,
+ resize: 'none',
+ color: 'inherit',
+ border: 0,
+ borderBottom: '1px dotted rgba(0, 136, 238, .2)',
+ borderRadius: 0,
+ outline: 'none',
+ overflow: 'hidden',
+ marginBottom: '0.75rem',
}
@@ -35,21 +35,27 @@ class TextareaAutosize extends React.Component {
constructor (props) {
super(props)
this.boundResize = this.resize.bind(this)
+ this.boundDebounceResize = this.debounceResize.bind(this)
this.boundOnChange = this.onChange.bind(this)
this.boundOnKeyDown = this.onKeyDown.bind(this)
this.state =
- { value: props.defaultValue
- , valid: true
- }
+ { value: props.defaultValue,
+ valid: true,
+ }
}
componentDidMount () {
- this.boundResize()
+ this.boundDebounceResize()
if (this.props.defaultFocus === true) {
this.refs.textarea.focus()
}
}
componentDidUpdate () {
- this.boundResize()
+ this.boundDebounceResize()
+ }
+ componentWillUnmount () {
+ if (this.debounce) {
+ clearTimeout(this.debounce)
+ }
}
componentWillReceiveProps (props) {
const {defaultValue, validator} = props
@@ -69,28 +75,26 @@ class TextareaAutosize extends React.Component {
autoCapitalize = 'none'
}
- return el('div'
- , { className: `TextareaAutosize ${this.props.className}`
- , style: containerStyle
- }
- , el('label'
- , { style: (valid ? labelStyle : labelStyleError)
- }
- , label
- , this.renderLink()
- , el('textarea'
- , { ref: 'textarea'
- , style: areaStyle
- , value: value || ''
- , placeholder
- , inputMode
- , autoCapitalize
- , onChange: this.boundOnChange
- , rows: 1
- , onFocus: this.boundResize
- , onKeyDown: this.boundOnKeyDown
- }
- )
+ return el('div',
+ {
+ className: `TextareaAutosize ${this.props.className}`,
+ style: containerStyle,
+ },
+ el('label',
+ {style: (valid ? labelStyle : labelStyleError)},
+ label,
+ this.renderLink(),
+ el('textarea', {
+ ref: 'textarea',
+ style: areaStyle,
+ value: value || '',
+ placeholder,
+ inputMode,
+ autoCapitalize,
+ onChange: this.boundOnChange,
+ rows: 1,
+ onKeyDown: this.boundOnKeyDown,
+ })
)
)
}
@@ -103,14 +107,14 @@ class TextareaAutosize extends React.Component {
return el('span', {}, ' - must be a valid url (http...)')
}
return el('a'
- , { href: value
- , target: '_blank'
- , rel: 'noreferrer noopener'
- , style:
- { marginLeft: '0.5rem'
- , textDecoration: 'none'
- }
- }
+ , { href: value,
+ target: '_blank',
+ rel: 'noreferrer noopener',
+ style:
+ { marginLeft: '0.5rem',
+ textDecoration: 'none',
+ },
+ }
, 'open'
)
}
@@ -119,6 +123,12 @@ class TextareaAutosize extends React.Component {
textarea.style.height = 'auto'
textarea.style.height = textarea.scrollHeight + 'px'
}
+ debounceResize () {
+ if (this.debounce) {
+ clearTimeout(this.debounce)
+ }
+ this.debounce = setTimeout(this.boundResize, 100)
+ }
onKeyDown (event) {
if (this.props.onKeyDown) {
this.props.onKeyDown(event)
@@ -146,22 +156,22 @@ class TextareaAutosize extends React.Component {
this.boundResize()
}
}
-TextareaAutosize.propTypes =
- { className: React.PropTypes.string
- , defaultValue: React.PropTypes.string
- , defaultFocus: React.PropTypes.bool
- , label: React.PropTypes.string
- , placeholder: React.PropTypes.string
- , inputMode: React.PropTypes.string
- , autoCapitalize: React.PropTypes.string
- , onChange: React.PropTypes.func
- , onKeyDown: React.PropTypes.func
- , multiline: React.PropTypes.bool
- , validator: React.PropTypes.func
- }
-TextareaAutosize.defaultProps =
- { multiline: false
- , inputMode: ''
- , autoCapitalize: 'sentences'
- }
+TextareaAutosize.propTypes = {
+ className: React.PropTypes.string,
+ defaultValue: React.PropTypes.string,
+ defaultFocus: React.PropTypes.bool,
+ label: React.PropTypes.string,
+ placeholder: React.PropTypes.string,
+ inputMode: React.PropTypes.string,
+ autoCapitalize: React.PropTypes.string,
+ onChange: React.PropTypes.func,
+ onKeyDown: React.PropTypes.func,
+ multiline: React.PropTypes.bool,
+ validator: React.PropTypes.func,
+}
+TextareaAutosize.defaultProps = {
+ multiline: false,
+ inputMode: '',
+ autoCapitalize: 'sentences',
+}
export default React.createFactory(TextareaAutosize)
diff --git a/src/components/widget-cta-view.js b/src/components/widget-cta-view.js
new file mode 100644
index 0000000..1709327
--- /dev/null
+++ b/src/components/widget-cta-view.js
@@ -0,0 +1,55 @@
+// If we need cover support for cta widget later,
+// don't add it here.
+
+import React, {createElement as el} from 'react'
+import Button from 'rebass/dist/Button'
+import ButtonOutline from 'rebass/dist/ButtonOutline'
+
+
+class WidgetCtaView extends React.Component {
+ render () {
+ const {initialBlock} = this.props
+ const {label, url} = initialBlock
+
+ return el('div',
+ {
+ className: 'WidgetView WidgetView-cta',
+ style: {
+ border: '1px solid silver',
+ borderRadius: 2,
+ padding: '1rem',
+ backgroundColor: 'white',
+ },
+ },
+ el(Button,
+ {
+ style: {
+ fontSize: '200%',
+ padding: '1.5rem',
+ marginBottom: '1rem',
+ display: 'block',
+ },
+ title: url,
+ big: true,
+ onClick: this.props.triggerEdit,
+ },
+ label || 'label',
+ ),
+ el(ButtonOutline,
+ {
+ onClick: this.props.triggerEdit,
+ },
+ 'Edit'
+ )
+ )
+ }
+}
+WidgetCtaView.propTypes =
+{ initialBlock: React.PropTypes.object.isRequired,
+ id: React.PropTypes.string.isRequired,
+ triggerEdit: React.PropTypes.func,
+}
+WidgetCtaView.contextTypes =
+ { store: React.PropTypes.object }
+
+export default React.createFactory(WidgetCtaView)
diff --git a/src/components/widget-cta.js b/src/components/widget-cta.js
index 526fc3a..f7582df 100644
--- a/src/components/widget-cta.js
+++ b/src/components/widget-cta.js
@@ -43,8 +43,8 @@ class WidgetCta extends React.Component {
this.toggleImport = () => {
const {showImport} = this.state
this.setState(
- { showImport: !showImport
- , importStatus: ''
+ { showImport: !showImport,
+ importStatus: '',
}
)
}
@@ -68,38 +68,38 @@ class WidgetCta extends React.Component {
const {label, url, canFrame} = this.state
return el('div'
- , { className: 'WidgetCta'
- }
+ , { className: 'WidgetCta',
+ }
, el('div'
- , { className: 'WidgetCta-metadata'
- , style: widgetLeftStyle
- }
+ , { className: 'WidgetCta-metadata',
+ style: widgetLeftStyle,
+ }
, el(TextareaAutosize
- , { label: 'Label'
- , key: 'label'
- , placeholder: 'Sign up now!'
- , defaultValue: label
- , onChange: this.changeLabel
- , style: {width: '100%'}
- }
+ , { label: 'Label',
+ key: 'label',
+ placeholder: 'Sign up now!',
+ defaultValue: label,
+ onChange: this.changeLabel,
+ style: {width: '100%'},
+ }
)
, el(TextareaAutosize
- , { label: 'Link'
- , key: 'url'
- , placeholder: 'https://...'
- , defaultValue: url
- , onChange: this.changeUrl
- , validator: isUrlOrBlank
- , style: {width: '100%'}
- }
+ , { label: 'Link',
+ key: 'url',
+ placeholder: 'https://...',
+ defaultValue: url,
+ onChange: this.changeUrl,
+ validator: isUrlOrBlank,
+ style: {width: '100%'},
+ }
)
, el(Checkbox
- , { key: 'canFrame'
- , label: 'Link can open in frame'
- , name: 'canFrame'
- , checked: (canFrame === true)
- , onChange: this.changeModal
- }
+ , { key: 'canFrame',
+ label: 'Link can open in frame',
+ name: 'canFrame',
+ checked: (canFrame === true),
+ onChange: this.changeModal,
+ }
)
, this.renderImport()
)
@@ -128,11 +128,11 @@ class WidgetCta extends React.Component {
return el('form'
, { onSubmit: this.boundImportHTML }
, el(TextareaAutosize
- , { label: 'HTML'
- , defaultValue: ''
- , defaultFocus: true
- , placeholder: '