-
-
Save jimdoescode/4c974cfae29d6a117b2a to your computer and use it in GitHub Desktop.
| var React = require("react"); | |
| var Router = require('react-router'); | |
| var GistForm = require("./GistForm.js"); | |
| var AppHeader = require("./AppHeader.js"); | |
| var AppFooter = require("./AppFooter.js"); | |
| var Gist = require("./Gist.js"); | |
| var Config = require("./Config.js"); | |
| var DefaultRoute = Router.DefaultRoute; | |
| var NotFoundRoute = Router.NotFoundRoute; | |
| var Route = Router.Route; | |
| var RouteHandler = Router.RouteHandler; | |
| var App = React.createClass({ | |
| render: function() { | |
| return ( | |
| <div className="app"> | |
| <AppHeader origin={Config.origin}/> | |
| <RouteHandler/> | |
| <AppFooter/> | |
| </div> | |
| ); | |
| } | |
| }); | |
| var GistRoute = React.createClass({ | |
| contextTypes: { | |
| router: React.PropTypes.func | |
| }, | |
| render: function() { | |
| return ( | |
| <Gist id={this.context.router.getCurrentParams().gistId}/> | |
| ); | |
| } | |
| }); | |
| var HomeRoute = React.createClass({ | |
| render: function() { | |
| return ( | |
| <div className="container main"> | |
| <section className="hero"> | |
| <h1> | |
| <div className="fa fa-flask fa-3x"/> | |
| <p>Up your Gist magic <i className="fa fa-magic"/></p> | |
| </h1> | |
| <p> | |
| CodeMana lets you comment in line on your Gists, simply click the line you want to talk about. | |
| It works entirely in your browser, only calling GitHub to post comments and retrieve Gists. | |
| </p> | |
| <p> | |
| If you're curious <a href="https://github.com/jimdoescode/codemana">peruse the code</a>. It's comprised mostly of React components. | |
| </p> | |
| <GistForm className="pure-form" showButton="true"/> | |
| </section> | |
| </div> | |
| ); | |
| } | |
| }); | |
| var FourOhFourRoute = React.createClass({ | |
| render: function() { | |
| return ( | |
| <div className="container main"> | |
| NOT FOUND!! | |
| </div> | |
| ); | |
| } | |
| }); | |
| var routes = ( | |
| <Route name="app" path={Config.root} handler={App}> | |
| <Route name="gist" path=":gistId" handler={GistRoute}/> | |
| <DefaultRoute handler={HomeRoute}/> | |
| <NotFoundRoute handler={FourOhFourRoute}/> | |
| </Route> | |
| ); | |
| Router.run(routes, Router.HistoryLocation, function (Handler) { | |
| React.render(<Handler/>, document.getElementById("mount-point")); | |
| }); |
| var React = require("react"); | |
| var GistForm = require("./GistForm.js"); | |
| module.exports = React.createClass({ | |
| getDefaultProps: function() { | |
| return {origin: window.location.origin}; | |
| }, | |
| updateStyle: function(event) { | |
| event.preventDefault(); | |
| document.getElementById('highlight-style').href = event.target.value; | |
| }, | |
| shouldComponentUpdate: function(newProps, newState) { | |
| return false; //No need to update this thing, it's static | |
| }, | |
| render: function() { | |
| return ( | |
| <header className="app-header"> | |
| <nav className="pure-menu pure-menu-horizontal pure-menu-fixed"> | |
| <div className="container"> | |
| <a className="pure-menu-heading pull-left logo" href={this.props.origin}> | |
| <span>CODE</span><i className="fa fa-flask"/><span>MANA</span> | |
| </a> | |
| <GistForm className="pure-form pull-left pure-u-2-3"/> | |
| <ul className="pure-menu-list pull-right"> | |
| <li className="pure-menu-item"> | |
| <select onChange={this.updateStyle}> | |
| <option value="css/default.css">Default Highlighting</option> | |
| <option value="css/funky.css">Funky Highlighting</option> | |
| <option value="css/okaidia.css">Okaidia Highlighting</option> | |
| <option value="css/dark.css">Dark Highlighting</option> | |
| </select> | |
| </li> | |
| </ul> | |
| </div> | |
| </nav> | |
| </header> | |
| ); | |
| } | |
| }); |
| module.exports = { | |
| root: '/', | |
| gistApi: 'https://api.github.com', | |
| origin: window.location.origin | |
| }; |
| var React = require("react"); | |
| module.exports = React.createClass({ | |
| getDefaultProps: function() { | |
| return { | |
| name: '', | |
| lines: [], | |
| comments: [], | |
| onCommentFormOpen: function() {}, | |
| onCommentFormCancel: function() {}, | |
| onCommentFormSubmit: function() {} | |
| }; | |
| }, | |
| render: function() { | |
| var rows = []; | |
| var lineCount = this.props.lines.length; | |
| for (var i=0; i < lineCount; i++) { | |
| var num = i + 1; | |
| if (this.props.comments[num] && this.props.comments[num].length > 0) { | |
| rows.push(<Line key={this.props.name + num} | |
| onClick={this.props.onCommentFormOpen} | |
| file={this.props.name} | |
| number={num} | |
| content={this.props.lines[i]} | |
| toggle={LineComments.generateNodeId(this.props.name, num)}/>); | |
| rows.push(<LineComments key={this.props.name + num + 'comments'} | |
| onEditOrReply={this.props.onCommentFormOpen} | |
| onCancel={this.props.onCommentFormCancel} | |
| onSubmit={this.props.onCommentFormSubmit} | |
| file={this.props.name} | |
| number={num} | |
| comments={this.props.comments[num]}/>); | |
| } else { | |
| rows.push(<Line key={this.props.name + num} | |
| onClick={this.props.onCommentFormOpen} | |
| file={this.props.name} | |
| number={num} | |
| content={this.props.lines[i]}/>); | |
| } | |
| } | |
| return ( | |
| <section className="code-file-container"> | |
| <table id={this.props.name} className="code-file"> | |
| <tbody> | |
| <tr className="spacer line"> | |
| <td className="line-marker"/> | |
| <td className="line-num"/> | |
| <td className="line-content"/> | |
| </tr> | |
| {rows} | |
| <tr className="spacer line"> | |
| <td className="line-marker"/> | |
| <td className="line-num"/> | |
| <td className="line-content"/> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </section> | |
| ); | |
| } | |
| }); | |
| var Line = React.createClass({ | |
| getDefaultProps: function() { | |
| return { | |
| number: 0, | |
| content: '', | |
| file: '', | |
| toggle: false, | |
| onClick: function() {} | |
| } | |
| }, | |
| //Lines only need to rerender when a new file is set. | |
| shouldComponentUpdate: function(newProps, newState) { | |
| return this.props.content !== newProps.content || | |
| this.props.file !== newProps.file || | |
| this.props.toggle !== newProps.toggle; | |
| }, | |
| render: function() { | |
| var toggleCol = this.props.toggle ? <td className="line-marker"><CommentToggle toggle={this.props.toggle}/></td> : <td className="line-marker"/> | |
| return ( | |
| <tr id={this.props.file+"-L"+this.props.number} className="line"> | |
| {toggleCol} | |
| <td className="line-num">{this.props.number}</td> | |
| <td className="line-content" onClick={this.props.onClick.bind(null, this.props.file, this.props.number, 0)}> | |
| <pre> | |
| <code dangerouslySetInnerHTML={{__html: this.props.content}}/> | |
| </pre> | |
| </td> | |
| </tr> | |
| ); | |
| } | |
| }); | |
| var LineComments = React.createClass({ | |
| statics: { | |
| generateNodeId: function(filename, number) { | |
| return filename + "-C" + number; | |
| } | |
| }, | |
| getDefaultProps: function() { | |
| return { | |
| number: 0, | |
| file: '', | |
| comments: [], | |
| onEditOrReply: function() {}, | |
| onCancel: function() {}, | |
| onSubmit: function() {} | |
| } | |
| }, | |
| render: function() { | |
| var comments = this.props.comments.map(function(comment) { | |
| return comment.showForm ? | |
| <CommentForm user={comment.user} text={comment.body} key="comment-form" onCancel={this.props.onCancel} onSubmit={this.props.onSubmit}/> : | |
| <Comment user={comment.user} text={comment.body} key={comment.id} onEditOrReply={this.props.onEditOrReply} id={this.props.file+"-L"+this.props.number+"-C"+comment.id}/>; | |
| }, this); | |
| return ( | |
| <tr id={LineComments.generateNodeId(this.props.file, this.props.number)} className="line comment-row"> | |
| <td className="line-marker"/> | |
| <td className="line-num"/> | |
| <td className="line-comments">{comments}</td> | |
| </tr> | |
| ); | |
| } | |
| }); | |
| var Comment = React.createClass({ | |
| getDefaultProps: function() { | |
| return { | |
| id: '', | |
| text: '', | |
| user: null, | |
| onEditOrReply: function() {} | |
| }; | |
| }, | |
| //Comments only need to rerender when new text is set. | |
| shouldComponentUpdate: function(newProps, newState) { | |
| return this.props.text !== newProps.text; | |
| }, | |
| render: function() { | |
| return ( | |
| <div className="line-comment"> | |
| <a className="avatar pull-left" href={this.props.user.html_url}><img src={this.props.user.avatar_url} alt=""/></a> | |
| <div className="pull-left content"> | |
| <header className="comment-header"> | |
| <a href={this.props.user.html_url}>{this.props.user.login}</a> | |
| </header> | |
| <p className="comment-body">{this.props.text}</p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| }); | |
| var CommentForm = React.createClass({ | |
| getDefaultProps: function() { | |
| return { | |
| text: '', | |
| user: null, | |
| onSubmit: function() {}, | |
| onCancel: function() {} | |
| } | |
| }, | |
| render: function() { | |
| return ( | |
| <div className="line-comment"> | |
| <a className="avatar pull-left" href={this.props.user.html_url}><img src={this.props.user.avatar_url} alt=""/></a> | |
| <div className="pull-left content"> | |
| <header className="comment-header"> | |
| <a href={this.props.user.html_url}>{this.props.user.login}</a> | |
| </header> | |
| <form action="#" onSubmit={this.props.onSubmit} className="comment-body"> | |
| <textarea name="text" placeholder="Enter your comment..." defaultValue={this.props.text}/> | |
| <button type="submit" className="pure-button button-primary"> | |
| <i className="fa fa-comment"/> Comment | |
| </button> | |
| <button type="button" className="pure-button button-error" onClick={this.props.onCancel}> | |
| <i className="fa fa-times-circle"/> Cancel | |
| </button> | |
| </form> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| }); | |
| var CommentToggle = React.createClass({ | |
| getInitialState: function() { | |
| return { | |
| display: 'none', | |
| symbolClass: this.props.closeIcon | |
| }; | |
| }, | |
| getDefaultProps: function() { | |
| return { | |
| toggle: '', | |
| closeIcon: 'fa-comment-o fa-flip-horizontal', | |
| openIcon: 'fa-comment fa-flip-horizontal' | |
| }; | |
| }, | |
| handleClick: function(event) { | |
| var elm = document.getElementById(this.props.toggle); | |
| var display = elm.style.display; | |
| event.preventDefault(); | |
| elm.style.display = this.state.display; | |
| this.setState({ | |
| symbolClass: display === 'none' ? this.props.closeIcon : this.props.openIcon, | |
| display: display | |
| }); | |
| }, | |
| render: function() { | |
| return ( | |
| <a href='#' onClick={this.handleClick}> | |
| <i className={"fa " + this.state.symbolClass + " fa-fw"}/> | |
| </a> | |
| ); | |
| } | |
| }); |
| var React = require("react"); | |
| var Qwest = require("qwest"); | |
| var Modal = require("react-modal"); | |
| var File = require("./File.js"); | |
| var Utils = require("./Utils.js"); | |
| var Spinner = require("./Spinner.js"); | |
| var Config = require("./Config.js"); | |
| Modal.setAppElement(document.getElementById("mount-point")); | |
| Modal.injectCSS(); | |
| Qwest.base = Config.gistApi; | |
| module.exports = React.createClass({ | |
| getHeaders: function() { | |
| var headers = {}; | |
| if (this.state.user !== null) | |
| headers["Authorization"] = 'Basic ' + btoa(this.state.user.login + ':' + this.state.user.password); | |
| return headers; | |
| }, | |
| getInitialState: function() { | |
| return { | |
| files: [], | |
| comments: [], | |
| openComment: null, | |
| showLoginModal: false, | |
| user: Utils.getUserFromStorage(sessionStorage), | |
| processing: true | |
| }; | |
| }, | |
| fetchGist: function(gistId) { | |
| var self = this; | |
| var options = { | |
| headers: this.getHeaders(), | |
| responseType: 'json' | |
| }; | |
| Qwest.get('/gists/'+gistId, null, options).then(function(xhr, gist) { | |
| var files = []; | |
| for (var name in gist.files) | |
| files.push(Utils.parseFile(gist.files[name])); | |
| if (self.isMounted()) | |
| self.setState({ | |
| files: files, | |
| processing: false | |
| }); | |
| }).catch(function(xhr, response, e) { | |
| console.log(xhr, response, e); | |
| alert('There was a problem fetching and or parsing this Gist.'); | |
| self.setState({processing: false}); | |
| }); | |
| Qwest.get('/gists/'+gistId+'/comments', null, options).then(function(xhr, comments) { | |
| var parsedComments = {}; | |
| var commentCount = comments.length; | |
| for (var i=0; i < commentCount; i++) { | |
| var parsed = Utils.parseComment(comments[i]); | |
| if (parsedComments[parsed.filename] === undefined) | |
| parsedComments[parsed.filename] = []; | |
| if (parsedComments[parsed.filename][parsed.line] === undefined) | |
| parsedComments[parsed.filename][parsed.line] = []; | |
| parsedComments[parsed.filename][parsed.line].push(parsed); | |
| } | |
| if (self.isMounted()) { | |
| self.setState({ | |
| comments: parsedComments | |
| }); | |
| } | |
| }).catch(function(xhr, response, e) { | |
| console.log(xhr, response, e); | |
| alert('There was a problem fetching the comments for this Gist.'); | |
| }); | |
| }, | |
| componentDidMount: function() { | |
| this.fetchGist(this.props.id); | |
| }, | |
| componentWillReceiveProps: function(newProps) { | |
| this.setState({processing: true}); | |
| this.fetchGist(newProps.id); | |
| }, | |
| componentDidUpdate: function(prevProps, prevState) { | |
| //If there is a hash specified then attempt to scroll there. | |
| if (window.location.hash) { | |
| var elm = document.getElementById(window.location.hash.substring(1)); | |
| if (elm) | |
| window.scrollTo(0, elm.offsetTop); | |
| } | |
| }, | |
| postGistComment: function(event) { | |
| var text = event.target.children.namedItem("text").value.trim(); | |
| var comments = this.state.comments; | |
| var open = this.state.openComment; | |
| var options = { | |
| headers: this.getHeaders(), | |
| dataType: 'json', | |
| responseType: 'json' | |
| }; | |
| event.preventDefault(); | |
| if (text !== "" && open !== null) { | |
| open.body = text; | |
| open.showForm = false; | |
| comments[open.filename][open.line].splice(open.replyTo, 1, open); | |
| //Send the comment to GitHub. We only need to handle the case where it doesn't make it | |
| Qwest.post('/gists/'+this.props.id+'/comments', {body: Utils.createCommentLink(this.props.id, open.filename, open.line) + ' ' + text}, options); | |
| this.setState({ | |
| comments: comments, | |
| openComment: null | |
| }); | |
| } | |
| }, | |
| insertCommentForm: function(filename, line, replyTo, event) { | |
| var comments = this.state.comments; | |
| var open = this.state.openComment; | |
| var newOpen = Utils.createComment(this.props.id, 0, filename, line, '', this.state.user, replyTo, true); | |
| if (this.state.user === null) { | |
| this.setState({showLoginModal: true}); | |
| return; | |
| } | |
| event.preventDefault(); | |
| if (open !== null) | |
| comments[open.filename][open.line].splice(open.replyTo, 1); | |
| if (!comments[filename]) | |
| comments[filename] = []; | |
| if (!comments[filename][line]) | |
| comments[filename][line] = []; | |
| if (open === null || open.filename !== newOpen.filename || open.line !== newOpen.line || open.replyTo !== newOpen.replyTo) { | |
| newOpen.id = comments[filename][line].length; | |
| comments[filename][line].splice(replyTo, 0, newOpen); | |
| } else { | |
| newOpen = null; | |
| } | |
| this.setState({ | |
| comments: comments, | |
| openComment: newOpen | |
| }); | |
| }, | |
| removeCommentForm: function(event) { | |
| var comments = this.state.comments; | |
| var open = this.state.openComment; | |
| event.preventDefault(); | |
| if (open !== null) { | |
| comments[open.filename][open.line].splice(open.replyTo, 1); | |
| this.setState({ | |
| comments: comments, | |
| openComment: null | |
| }); | |
| } | |
| }, | |
| handleLogin: function(user) { | |
| this.setState({ | |
| user: user, | |
| showLoginModal: false | |
| }); | |
| }, | |
| closeModal: function(event) { | |
| event.preventDefault(); | |
| this.setState({showLoginModal: false}); | |
| }, | |
| render: function() { | |
| var body = this.state.processing ? | |
| <Spinner/> : this.state.files.map(function(file) { | |
| return <File onCommentFormOpen={this.insertCommentForm} | |
| onCommentFormCancel={this.removeCommentForm} | |
| onCommentFormSubmit={this.postGistComment} | |
| key={file.name} | |
| name={file.name} | |
| lines={file.parsedLines} | |
| comments={this.state.comments[file.name]}/> | |
| }, this); | |
| return ( | |
| <div className="container main"> | |
| <LoginModal show={this.state.showLoginModal} onSuccess={this.handleLogin} onClose={this.closeModal}/> | |
| {body} | |
| </div> | |
| ); | |
| } | |
| }); | |
| var LoginModal = React.createClass({ | |
| getDefaultProps: function() { | |
| return { | |
| show: false, | |
| onSuccess: function(user) {}, | |
| onClose: function() {} | |
| }; | |
| }, | |
| getInitialState: function() { | |
| return { | |
| processing: false | |
| }; | |
| }, | |
| attemptLogin: function(event) { | |
| var username = event.target.elements.namedItem("username").value.trim(); | |
| var password = event.target.elements.namedItem("password").value; | |
| var store = event.target.elements.namedItem("store").checked; | |
| var self = this; | |
| event.preventDefault(); | |
| if (username && password) { | |
| var options = { | |
| headers: { | |
| Authorization: 'Basic ' + btoa(username + ':' + password) | |
| }, | |
| responseType: 'json' | |
| }; | |
| this.setState({processing: true}); | |
| Qwest.get('/user', null, options).then(function(xhr, user) { | |
| user.password = password; | |
| if (store) | |
| Utils.saveUserToStorage(user, sessionStorage); | |
| self.props.onSuccess(user); | |
| }).complete(function(xhr, user) { | |
| self.setState({processing: false}); | |
| }); | |
| } | |
| }, | |
| render: function() { | |
| var form = ( | |
| <form className="pure-form pure-form-stacked" onSubmit={this.attemptLogin}> | |
| <fieldset> | |
| <input name="username" className="pure-input-1" type="text" placeholder="GitHub User Name..." required="true"/> | |
| <input name="password" className="pure-input-1" type="password" placeholder="GitHub Password or Token..." required="true"/> | |
| <label><input name="store" type="checkbox"/> Store in Memory</label> | |
| </fieldset> | |
| <fieldset> | |
| <button type="submit" className="pure-button button-primary"><i className="fa fa-save"/> Save</button> | |
| <button className="pure-button button-error" onClick={this.props.onClose}><i className="fa fa-times-circle"/> Cancel</button> | |
| </fieldset> | |
| </form> | |
| ); | |
| return ( | |
| <Modal isOpen={this.props.show} onRequestClose={this.props.onClose} className="react-modal-content" overlayClassName="react-modal-overlay"> | |
| <h2><i className="fa fa-github"/> GitHub Access</h2> | |
| <p>To leave a comment you need to enter your GitHub user name and GitHub password. This is <strong>only</strong> used to post Gist comments to GitHub.</p> | |
| <p>If you prefer not to enter your password you can use a <a target="_blank" href="https://github.com/settings/tokens/new">personal access token</a>. Make sure it has Gist access.</p> | |
| <hr/> | |
| { this.state.processing ? <Spinner className="fa-github-alt"/> : form } | |
| </Modal> | |
| ); | |
| } | |
| }); |
| var React = require("react"); | |
| var Router = require('react-router'); | |
| module.exports = React.createClass({ | |
| mixins: [Router.Navigation], | |
| getDefaultProps: function() { | |
| return { | |
| className: "", | |
| showButton: false | |
| }; | |
| }, | |
| handleSubmit: function(event) { | |
| event.preventDefault(); | |
| var gistId = event.target.elements.namedItem("gistId").value.trim(); | |
| if (gistId.length > 0) | |
| this.transitionTo('gist', {gistId: gistId}); | |
| }, | |
| render: function() { | |
| var body = ( | |
| this.props.showButton ? | |
| <fieldset> | |
| <input name="gistId" type="text" className="pure-input-1-3" placeholder="Enter a Gist ID..." required="true"/> | |
| <button type="submit" className="pure-button button-primary">Go <i className="fa fa-sign-in"/></button> | |
| </fieldset> | |
| : | |
| <input className="pure-input-1-3" name="gistId" type="text" placeholder="Enter a Gist ID..." required="true"/> | |
| ); | |
| return ( | |
| <form className={this.props.className} onSubmit={this.handleSubmit} action="#">{body}</form> | |
| ); | |
| } | |
| }); | |
| var React = require("react"); | |
| module.exports = React.createClass({ | |
| getDefaultProps: function() { | |
| return { | |
| className: 'fa-spinner' | |
| }; | |
| }, | |
| render: function() { | |
| var classes = 'fa ' + this.props.className + ' fa-spin fa-5x'; | |
| return ( | |
| <p className="spinner"><i className={classes}/></p> | |
| ); | |
| } | |
| }); |
| var Prism = require("./Prism.js"); | |
| module.exports = { | |
| tokenizeNewLines: function(str) { | |
| var tokens = []; | |
| var strlen = str.length; | |
| var lineCount = 0; | |
| for (var i=0; i < strlen; i++) | |
| { | |
| if (tokens[lineCount]) | |
| tokens[lineCount] += str[i]; | |
| else | |
| tokens[lineCount] = str[i]; | |
| if (str[i] === '\n') | |
| lineCount++; | |
| } | |
| return tokens; | |
| }, | |
| tokenize: function(code, lang) { | |
| var processed = []; | |
| var tokens = Prism.tokenize(code, lang); | |
| var token = tokens.shift(); | |
| while (token) | |
| { | |
| var isObj = typeof token === 'object'; | |
| var lines = this.tokenizeNewLines(isObj ? token.content : token); | |
| var count = lines.length; | |
| for (var i=0; i < count; i++) | |
| { | |
| if (isObj) | |
| processed.push(new Prism.Token(token.type, Prism.util.encode(lines[i]), token.alias)); | |
| else | |
| processed.push(Prism.util.encode(lines[i])); | |
| } | |
| token = tokens.shift(); | |
| } | |
| return processed; | |
| }, | |
| syntaxHighlight: function(code, lang) { | |
| var lineCount = 0; | |
| var lines = ['']; | |
| var prismLang = this.getPrismCodeLanguage(lang); | |
| var tokens = this.tokenize(code, prismLang); | |
| var token = tokens.shift(); | |
| while (token) | |
| { | |
| code = (typeof token === 'object') ? Prism.Token.stringify(token, prismLang) : token; | |
| lines[lineCount] += code.replace(/\n/g, ''); | |
| if (code.indexOf('\n') !== -1) | |
| lines[++lineCount] = ''; | |
| token = tokens.shift(); | |
| } | |
| return lines; | |
| }, | |
| getPrismCodeLanguage: function(gistLang) { | |
| var lang = gistLang.toLowerCase().replace(/#/, 'sharp').replace(/\+/g, 'p'); | |
| if (Prism.languages[lang]) { | |
| return Prism.languages[lang]; | |
| } | |
| console.log("CodeMana Error - Prism doesn't support the language: "+gistLang+". Help them and us out by adding it http://prismjs.com/"); | |
| throw ({msg: "CodeMana Error - Prism doesn't support the language: " + gistLang}); | |
| }, | |
| getUserFromStorage: function(store) { | |
| return JSON.parse(store.getItem('user')); | |
| }, | |
| saveUserToStorage: function(user, store) { | |
| store.setItem('user', JSON.stringify(user)); | |
| }, | |
| createComment: function(gistId, commentId, filename, lineNumber, commentBody, commentUser, replyTo, showForm) { | |
| return { | |
| gistId: gistId, | |
| id: commentId, | |
| filename: filename, | |
| line: parseInt(lineNumber, 10), | |
| body: commentBody, | |
| user: commentUser, | |
| replyTo: replyTo, | |
| showForm: showForm | |
| }; | |
| }, | |
| createFile: function(name, parsedLines) { | |
| return { | |
| name: name, | |
| parsedLines: parsedLines | |
| }; | |
| }, | |
| parseComment: function(comment) { | |
| //Annoyingly I couldn't get a single regex to separate everything out... | |
| var split = comment.body.match(/(\S+)\s(.*)/); | |
| var data = split[1].match(/http:\/\/codemana\.com\/(.*)#(.+)-L(\d+)/); | |
| return data !== null ? this.createComment(data[1], comment.id, data[2], parseInt(data[3], 10), split[2], comment.user, 0, false) : null; | |
| }, | |
| parseFile: function(file) { | |
| var lines = this.syntaxHighlight(file.content, file.language); | |
| return this.createFile(file.filename, lines) | |
| }, | |
| createCommentLink: function(id, filename, lineNumber) { | |
| return 'http://codemana.com/'+id+'#'+filename+'-L'+lineNumber; | |
| } | |
| }; |
https://codemana.com/4c974cfae29d6a117b2a#Utils.js-L116 Is there some way to make this configurable so that you don't have to hardcode the domain?
https://codemana.com/4c974cfae29d6a117b2a#AppFooter.js-L6 You should include links to the github repo in the footer.
https://codemana.com/4c974cfae29d6a117b2a#App.js-L9 Whoops I wanted to say something different
https://codemana.com/4c974cfae29d6a117b2a#App.js-L9 Testing stuff again
http://localhost:3000/4c974cfae29d6a117b2a#App.js-L12 Testing some stuff
http://localhost:3000/4c974cfae29d6a117b2a#App.js-L12 Replying to this one.
http://localhost:3000/4c974cfae29d6a117b2a#App.js-L17 Testing the new comment form edit
testing it now
http://localhost:3000/4c974cfae29d6a117b2a#App.js-L17 Replying to a comment
http://localhost:3000/4c974cfae29d6a117b2a#Config.js-L5 Make it scroll a little
https://codemana.com/4c974cfae29d6a117b2a#App.js-L65 Need a better 404 page.