diff --git a/app/classifier/drawing-tools/circle.cjsx b/app/classifier/drawing-tools/circle.cjsx index 2a8640f8c2..52ae610ec4 100644 --- a/app/classifier/drawing-tools/circle.cjsx +++ b/app/classifier/drawing-tools/circle.cjsx @@ -20,12 +20,18 @@ module.exports = React.createClass r: 0 angle: 0 + initStart: -> + _inProgress: true + initMove: ({x, y}, mark) -> distance = @getDistance mark.x, mark.y, x, y angle = @getAngle mark.x, mark.y, x, y r: distance angle: angle + initRelease: -> + _inProgress: false + initValid: (mark) -> mark.r > MINIMUM_RADIUS diff --git a/app/classifier/drawing-tools/ellipse.cjsx b/app/classifier/drawing-tools/ellipse.cjsx index 2cd0c1990c..83b2c5cba6 100644 --- a/app/classifier/drawing-tools/ellipse.cjsx +++ b/app/classifier/drawing-tools/ellipse.cjsx @@ -21,6 +21,9 @@ module.exports = React.createClass ry: 0 angle: 0 + initStart: -> + _inProgress: true + initMove: ({x, y}, mark) -> distance = @getDistance mark.x, mark.y, x, y angle = @getAngle mark.x, mark.y, x, y @@ -28,6 +31,9 @@ module.exports = React.createClass ry: distance * DEFAULT_SQUASH angle: angle + initRelease: -> + _inProgress: false + initValid: (mark) -> mark.rx > MINIMUM_RADIUS diff --git a/app/classifier/drawing-tools/line.cjsx b/app/classifier/drawing-tools/line.cjsx index 5af176980e..899d9ee72a 100644 --- a/app/classifier/drawing-tools/line.cjsx +++ b/app/classifier/drawing-tools/line.cjsx @@ -17,10 +17,16 @@ module.exports = React.createClass x2: x y2: y + initStart: -> + _inProgress: true + initMove: ({x, y}) -> x2: x y2: y + initRelease: -> + _inProgress: false + initValid: (mark) -> {x1, y1, x2, y2} = mark DrawingToolRoot.distance(x1, y1, x2, y2) > MINIMUM_LENGTH diff --git a/app/classifier/drawing-tools/point.cjsx b/app/classifier/drawing-tools/point.cjsx index a36c290cdf..de1f5fbb8c 100644 --- a/app/classifier/drawing-tools/point.cjsx +++ b/app/classifier/drawing-tools/point.cjsx @@ -16,9 +16,15 @@ module.exports = React.createClass defaultValues: ({x, y}) -> {x, y} + initStart: -> + _inProgress: true + initMove: ({x, y}) -> {x, y} + initRelease: -> + _inProgress: false + getDeleteButtonPosition: -> theta = (DELETE_BUTTON_ANGLE) * (Math.PI / 180) x: (SELECTED_RADIUS / @props.scale.horizontal) * Math.cos theta diff --git a/app/classifier/drawing-tools/polygon.cjsx b/app/classifier/drawing-tools/polygon.cjsx index 747ac394a6..8ed98a0472 100644 --- a/app/classifier/drawing-tools/polygon.cjsx +++ b/app/classifier/drawing-tools/polygon.cjsx @@ -22,6 +22,7 @@ module.exports = React.createClass initStart: ({x, y}, mark) -> mark.points.push {x, y} points: mark.points + _inProgress: true initMove: ({x, y}, mark) -> mark.points[mark.points.length - 1] = {x, y} @@ -112,6 +113,7 @@ module.exports = React.createClass document.removeEventListener 'mousemove', @handleMouseMove @props.mark.closed = true + @props.mark._inProgress = false @props.onChange() handleMainDrag: (e, d) -> diff --git a/app/classifier/drawing-tools/rectangle.cjsx b/app/classifier/drawing-tools/rectangle.cjsx index 11a3ed912e..12b8f7cbe2 100644 --- a/app/classifier/drawing-tools/rectangle.cjsx +++ b/app/classifier/drawing-tools/rectangle.cjsx @@ -21,7 +21,7 @@ module.exports = React.createClass initStart: ({x, y}, mark) -> @initCoords = {x, y} - {x, y} + {x, y, _inProgress: true} initMove: (cursor, mark) -> if cursor.x > @initCoords.x @@ -40,6 +40,9 @@ module.exports = React.createClass {x, y, width, height} + initRelease: -> + _inProgress: false + initValid: (mark) -> mark.width > MINIMUM_SIZE and mark.height > MINIMUM_SIZE diff --git a/app/classifier/drawing-tools/root.cjsx b/app/classifier/drawing-tools/root.cjsx index 2e6e73f35d..af11827549 100644 --- a/app/classifier/drawing-tools/root.cjsx +++ b/app/classifier/drawing-tools/root.cjsx @@ -52,7 +52,7 @@ module.exports = React.createClass {@props.children} - {if toolProps.selected and toolProps.details? and toolProps.details.length isnt 0 + {if toolProps.selected and not toolProps.mark._inProgress and toolProps.details? and toolProps.details.length isnt 0 tasks = require '../tasks' detailsAreComplete = toolProps.details.every (detailTask, i) => diff --git a/app/classifier/index.cjsx b/app/classifier/index.cjsx index 8c7ddc6471..c517dd88a0 100644 --- a/app/classifier/index.cjsx +++ b/app/classifier/index.cjsx @@ -8,6 +8,7 @@ tasks = require './tasks' preloadSubject = require '../lib/preload-subject' PromiseRenderer = require '../components/promise-renderer' TriggeredModalForm = require 'modal-form/triggered' +TutorialButton = require './tutorial-button' isAdmin = require '../lib/is-admin' Tutorial = require '../lib/tutorial' @@ -122,11 +123,7 @@ Classifier = React.createClass
-
- - - -
diff --git a/app/router.cjsx b/app/router.cjsx index d2aba75cfa..2348202e51 100644 --- a/app/router.cjsx +++ b/app/router.cjsx @@ -103,6 +103,7 @@ module.exports = + @@ -121,4 +122,3 @@ module.exports = - diff --git a/app/subjects/comment-form.cjsx b/app/subjects/comment-form.cjsx new file mode 100644 index 0000000000..23aaabf0ee --- /dev/null +++ b/app/subjects/comment-form.cjsx @@ -0,0 +1,98 @@ +React = require 'react' +{Navigation, Link} = require '@edpaget/react-router' +talkClient = require '../api/talk' +NewDiscussionForm = require '../talk/discussion-new-form' +QuickSubjectCommentForm= require '../talk/quick-subject-comment-form' +PromiseRenderer = require '../components/promise-renderer' +Loading = require '../components/loading-indicator' +SignInPrompt = require '../partials/sign-in-prompt' +alert = require '../lib/alert' + +module?.exports = React.createClass + displayName: 'SubjectCommentForm' + mixins: [Navigation] + + componentWillMount: -> + Promise.all([@getBoards(), @getSubjectDefaultBoard()]).then => + @setState loading: false + + getInitialState: -> + loading: true + tab: 0 + + getBoards: -> + talkClient.type('boards').get(section: @props.section, subject_default: false).then (boards) => + @setState {boards} + + getSubjectDefaultBoard: -> + talkClient.type('boards').get(section: @props.section, subject_default: true).then ([subjectDefaultBoard]) => + @setState {subjectDefaultBoard} + + onCreateDiscussion: (createdDiscussion) -> + {owner, name} = @props.params + board = createdDiscussion.board_id + discussion = createdDiscussion.id + @transitionTo 'project-talk-discussion', {owner, name, board, discussion} + + linkToClassifier: (text) -> + [owner, name] = @props.project.slug.split('/') + {text} + + popup: -> + alert (resolve) -> + + loginPrompt: -> +

+ Please{' '} + {' '} + to contribute to subject discussions +

+ + notSetup: -> +

There are no discussion boards setup for this project yet. Check back soon!

+ + quickComment: -> + if @state.subjectDefaultBoard + + else +

+ There is no default board for subject comments setup yet, Please{' '} + {' '} + or {@linkToClassifier('return to classifying')} +

+ + startDiscussion: -> + + + renderTab: -> + switch @state.tab + when 0 then @quickComment() + when 1 then @startDiscussion() + + render: -> + return if @state.loading + return @notSetup() unless @state.boards + return @loginPrompt() unless @props.user + +
+
+
+
+
@setState({tab: 0})}> + Add a note about this subject +
+ +
@setState({tab: 1})}> + Start a new discussion +
+
+
+
+ + {@renderTab()} +
diff --git a/app/subjects/comment-list.cjsx b/app/subjects/comment-list.cjsx new file mode 100644 index 0000000000..76113480b2 --- /dev/null +++ b/app/subjects/comment-list.cjsx @@ -0,0 +1,52 @@ +React = require 'react' +talkClient = require '../api/talk' +Comment = require '../talk/comment' +Paginator = require '../talk/lib/paginator' +Loading = require '../components/loading-indicator' + +module?.exports = React.createClass + displayName: 'SubjectCommentList' + + componentWillMount: -> + @getComments() + + componentWillReceiveProps: (nextProps) -> + if nextProps.query.page isnt @props.query.page + @getComments nextProps.query.page + + getDefaultProps: -> + query: page: 1 + + getInitialState: -> + meta: { } + + getComments: (page = @props.query.page) -> + query = + section: @props.section + focus_id: @props.subject.id + focus_type: 'Subject' + page: page or 1 + sort: '-created_at' + + talkClient.type('comments').get(query).then (comments) => + meta = comments[0]?.getMeta() or { } + @setState {comments, meta} + + render: -> + return unless @state.comments + + if @state.comments.length > 0 +
+

Comments:

+
+ {for comment in @state.comments + } +
+ + {if +@state.meta.page_count > 1 + } +
+ else +

There are no comments yet

diff --git a/app/subjects/discussion-list.cjsx b/app/subjects/discussion-list.cjsx new file mode 100644 index 0000000000..cac077f3b9 --- /dev/null +++ b/app/subjects/discussion-list.cjsx @@ -0,0 +1,42 @@ +React = require 'react' +talkClient = require '../api/talk' +DiscussionPreview = require '../talk/discussion-preview' +Loading = require '../components/loading-indicator' + +module?.exports = React.createClass + displayName: 'SubjectDiscussionList' + + componentWillMount: -> + @getDiscussions() + + getInitialState: -> + discussions: null + + getDiscussions: -> + query = + section: @props.section + focus_id: @props.subject.id + focus_type: 'Subject' + page_size: 20 + sort: '-created_at' + + talkClient.type('discussions').get(query).then (discussions) => + @setState {discussions} + + render: -> + return unless @state.discussions + + if @state.discussions.length > 0 +
+

Discussions:

+
+ {for discussion in @state.discussions + } +
+
+ else +

There are no discussions yet

diff --git a/app/subjects/index.cjsx b/app/subjects/index.cjsx index aaefc2072b..4d8c27c26b 100644 --- a/app/subjects/index.cjsx +++ b/app/subjects/index.cjsx @@ -1,156 +1,52 @@ React = require 'react' apiClient = require '../api/client' talkClient = require '../api/talk' -getSubjectLocation = require '../lib/get-subject-location' -FavoritesButton = require '../collections/favorites-button' -PromiseRenderer = require '../components/promise-renderer' SubjectViewer = require '../components/subject-viewer' -NewDiscussionForm = require '../talk/discussion-new-form' -CommentLink = require '../talk/comment-link' -projectSection = require '../talk/lib/project-section' -parseSection = require '../talk/lib/parse-section' -QuickSubjectCommentForm= require '../talk/quick-subject-comment-form' -{Navigation, Link} = require '@edpaget/react-router' -{Markdown} = require 'markdownz' -alert = require '../lib/alert' -SignInPrompt = require '../partials/sign-in-prompt' -Comment = require '../talk/comment' -Paginator = require '../talk/lib/paginator' PopularTags = require '../talk/popular-tags' ActiveUsers = require '../talk/active-users' ProjectLinker = require '../talk/lib/project-linker' - -indexOf = (elem) -> - (elem while elem = elem.previousSibling).length - -promptToSignIn = -> - alert (resolve) -> +SubjectCommentForm = require './comment-form' +SubjectCommentList = require './comment-list' +SubjectDiscussionList = require './discussion-list' +SubjectMentionList = require './mention-list' module?.exports = React.createClass displayName: 'Subject' - mixins: [Navigation] getInitialState: -> subject: null - tab: 0 - comments: [] - commentsMeta: {} - - getDefaultProps: -> - query: page: 1 componentWillMount: -> - @setSubject().then(@setComments) + @setSubject() componentWillReceiveProps: (nextProps) -> - if nextProps.params?.id isnt @props.params?.id - @setSubject().then(@setComments) - - if nextProps.query.page isnt @props.query.page - @setComments(@state.subject, nextProps.query.page) + @setSubject() if nextProps.params?.id isnt @props.params?.id setSubject: -> - subjectId = @props.params?.id.toString() - apiClient.type('subjects').get(subjectId) - .then (subject) => - @setState {subject} - subject - - setComments: (subject = @state.subject, page = @props.query.page ? 1) -> - talkClient.type('comments') - .get({focus_id: subject.id, focus_type: 'Subject', page, sort: '-created_at'}) - .then (comments) => - commentsMeta = comments[0]?.getMeta() - @setState {comments, commentsMeta} - - comment: (comment, i) -> - - - onCreateDiscussion: (discussion) -> - projectId = parseSection(discussion.section) - apiClient.type('projects').get(projectId).then (project) => - [owner, name] = project.slug.split('/') - @transitionTo('project-talk-discussion', {owner: owner, name: name, board: discussion.board_id, discussion: discussion.id}) - - linkToClassifier: (text) -> - [owner, name] = @props.project.slug.split('/') - {text} + subjectId = @props.params.id.toString() + apiClient.type('subjects').get(subjectId).then (subject) => + @setState {subject} render: -> - {subject, comments, commentsMeta} = @state -
- {if subject -
-

Subject {subject.id}

- - - - {if comments?.length -
-

Comments mentioning this subject:

-
{comments.map(@comment)}
- - -
- else -

There are no comments focused on this subject

} - - {if @props.user - {# TODO remove subject.get('project'), replace with params but browser freezes on get to projects with slug} - project = subject.get('project') - boards = project.then (project) -> talkClient.type('boards').get(section: projectSection(project), subject_default: false) - subjectDefaultBoard = project.then (project) -> talkClient.type('boards').get(section: projectSection(project), subject_default: true) - - {([boards, subjectDefaultBoard]) => - defaultExists = subjectDefaultBoard.length - if boards.length or defaultExists -
-
-
-
-
@setState({tab: 0})}> - Add a note about this subject -
- -
@setState({tab: 1})}> - Start a new discussion -
-
-
-
-
- {if @state.tab is 0 - if defaultExists - - else -

- There is no default board for subject comments setup yet, Please{' '} - {' '} - or {@linkToClassifier('return to classifying')} -

- else if @state.tab is 1 - - } -
-
- else -

There are no discussion boards setup for this project yet. Check back soon!

- }
- else -

Please to contribute to subject discussions

} -
} +
+ {if @state.subject +
+

Subject {@state.subject.id}

+ + + + + + + +
} +
+ @getMentions() + + getInitialState: -> + mentions: null + + getMentions: -> + query = + section: @props.section + mentionable_id: @props.subject.id + mentionable_type: 'Subject' + sort: '-created_at' + page_size: 20 + include: 'comment' + + talkClient.type('mentions').get(query).then (mentions) => + Promise.all mentions.map (mention) -> + mention.get('comment').then (comment) -> + mention.update {comment} + .then (mentions) => + @setState {mentions} + + render: -> + return unless @state.mentions + + if @state.mentions.length > 0 +
+

Mentions:

+
+ {for mention in @state.mentions + } +
+
+ else + diff --git a/css/main-footer.styl b/css/main-footer.styl index 8f76c6afba..996a3b1a06 100644 --- a/css/main-footer.styl +++ b/css/main-footer.styl @@ -19,14 +19,16 @@ FOOTER_TEXT_COLOR = rgba(white, 0.5) color: rgba(white, 0.75) .main-logo - margin-bottom: 2.7em + margin: 0 6.25vw 2.7em 0 + color: white .main-logo-link color: rgba(white, 0.25) font-size: 18px + margin-bottom: 2.7em &:hover - color: rgba(white, 0.4) + color: white .main-footer-flex display: flex @@ -34,35 +36,33 @@ FOOTER_TEXT_COLOR = rgba(white, 0.5) justify-content: center max-width: 960px width: 100% - - @media screen and (max-width: 768px) - box-sizing: border-box .site-map - display: inline-flex - flex: 1 0 auto + display: flex + flex-wrap: wrap justify-content: center .site-map-section - display: inline-flex - flex-direction: column - flex-wrap: wrap + display: flex margin: 0 6.25vw 2.7em 0 - - &:first-of-type - margin-left: 6.25vw + flex: 1 0 auto + flex-direction: column h6 color: COPY font-size: inherit a - white-space: nowrap display: block + white-space: nowrap .social-media + display: block + // flex-basis: 100px + text-align: center a margin: 0.5em + display: inline-block .footer-sole background: darken(MAIN_FOOTER_BACKGROUND, 20%) diff --git a/css/subject-page.styl b/css/subject-page.styl index eb9d06ecb2..f443ebdf6f 100644 --- a/css/subject-page.styl +++ b/css/subject-page.styl @@ -1,10 +1,20 @@ -.subject-page +.talk .subject-page margin: 0 auto .subject-container width: 100% img max-width: 100% + .talk-discussion-preview + .subject-preview + display: none + + .talk-comment-box + .talk-comment-focus-image + + .talk-comment-form .talk-comment-image-select-button + display: none + .talk-comment-link a text-decoration: none diff --git a/css/talk.styl b/css/talk.styl index 95b4b56f07..cc4da955af 100644 --- a/css/talk.styl +++ b/css/talk.styl @@ -19,6 +19,9 @@ COPY_GREY_LIGHT = #afaeae box-sizing: border-box color: COPY + img + max-width: 100% + .talk-text-input // shared styles among talk text inputs border: none diff --git a/public/assets/about-hero.png b/public/assets/about-hero.png index 7aff4f22c4..0aa440cd9a 100644 Binary files a/public/assets/about-hero.png and b/public/assets/about-hero.png differ