0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-03-22 08:58:11 +00:00

Merge branch 'v2'

This commit is contained in:
Scott Tolksdorf
2016-05-16 20:59:40 -04:00
232 changed files with 24938 additions and 2643 deletions

View File

@@ -1,5 +1,47 @@
# changelog
### Saturday, 14/05/2016 - v2.0.0 (finally!)
I've been working on v2 for a *very* long time. I want to thank you guys for being paitent.
It started rather small, but as more and more features were added, I decided to just wait until everything was polished.
Massive changelog incoming:
#### Brews
- New flow for creating new brews. Before creating a new brew would immedaitely create a brew in the database and let you edit it. Many people would create a new brew just to experiment and close it, which lead to many "abandoned brews" (see the Under the hood section below). This started eating up a ton of database space. You now have to manually save a new brew for the first time, however Homebrewery will store anything you don't have saved in local storage, just in case your browser crashes or whatever, it will load that up when you go back to `/homebrew/new`
- Black blocking edges around notes and stat blocks when printing to PDFs have been fixed
- Borders sometimes not showing up in the second column have been fixed
- All pseudo-element borders have been replaced with reliable border images.
- Chrome can finally print to PDF as good as Chrome Canary! Updating instructions.
- Added a little page number box.
- Added in a new editable Brew Title. This will be shown in the navbar on share pages, and will default to the file name when you save as PDF. All exsisting brews will be defaulted with an empty title.
- Mutliline lists render better now
- Firefox rendering has been slithgly improved. Firefox and Chrome render things **slightly** differently, over the course of a brew, these little changes add up and lead to very noticable rendering differences between the browsers. I'm trying my best to get Firefox rendering better, but it's a difficult problem.
- A bunch of you have wanted to donate to me. I am super flattered by that. I created a [patreon page](https://www.patreon.com/stolksdorf). If you feel like helping out, head here :)
#### Under the Hood Stuff
- Setup a proper staging environment. Will be using this for tests, and hopefully getting the community to help me test future versions
- Server-side prerendering now much faster
- Regular weekly database back-ups. Your brews are safe!
- Database is now uniquely indexed on both editId and shareId, page loads/saving should be much faster
- Improved Admin console. This helps me answer people's questions about issues with their brews
- Added a whole querying/pagniation API that I can use for stats and answering questions
- Clearing out "Abandoned" brews (smaller than a tweet and haven't been viewed for a week). These account for nearly a third of all stored brews.
#### Interface
- Added in a whole new editor with syntax highlighting for markdown
- Built a splitpane! Remembers where you left the split in between sessions
- Re-organized the snippets into a hierarchical groups. Should be much easier to find what you need
- Partial page rendering is working. The Homebrewery will now only load the viewable pages, and any page with `<style>` tags on them. If you are working on a large brew you should notice *significant* performance improvements
- Edit page saving interface has been improved significantly. Auto-saves after 3 seconds on inactivity, now allows user to save at anytime. Will stop the tab from closing witha pop-up if there are unsaved changes.
- Navbar and overall style has been improved and spacing made more consistent
- Elements under the hood are way more organized and should behaviour much more reliably in many sizes.
- Source now opens to it's own route `/source/:sharedId` instead of just a window. Now easier to share, and won't be blocked by some browsers.
- Print page now auto-opens print dialog. If you want to share your print page link, just remove the `?dialog=true` parameter and it won't open the dialog.
\page
### Wednesday, 20/04/2016
- A lot of admin improvements. Pagninated brew table

View File

@@ -1,8 +1,8 @@
@import 'naturalCrit/styles/reset.less';
@import 'naturalCrit/styles/elements.less';
@import 'naturalCrit/styles/animations.less';
@import 'naturalCrit/styles/colors.less';
@import 'naturalCrit/styles/tooltip.less';
@import 'naturalcrit/styles/reset.less';
@import 'naturalcrit/styles/elements.less';
@import 'naturalcrit/styles/animations.less';
@import 'naturalcrit/styles/colors.less';
@import 'naturalcrit/styles/tooltip.less';
@import 'font-awesome/css/font-awesome.css';

View File

@@ -0,0 +1,93 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Markdown = require('marked');
var PAGE_HEIGHT = 1056 + 30;
var BrewRenderer = React.createClass({
getDefaultProps: function() {
return {
text : ''
};
},
getInitialState: function() {
return {
viewablePageNumber: 0,
height : 0
};
},
totalPages : 0,
height : 0,
componentDidMount: function() {
this.setState({
height : this.refs.main.parentNode.clientHeight
});
},
handleScroll : function(e){
this.setState({
viewablePageNumber : Math.floor(e.target.scrollTop / PAGE_HEIGHT)
});
},
//Implement later
scrollToPage : function(pageNumber){
},
shouldRender : function(pageText, index){
var viewIndex = this.state.viewablePageNumber;
if(index == viewIndex - 1) return true;
if(index == viewIndex) return true;
if(index == viewIndex + 1) return true;
//Check for style tages
if(pageText.indexOf('<style>') !== -1) return true;
return false;
},
renderPageInfo : function(){
return <div className='pageInfo'>
{this.state.viewablePageNumber + 1} / {this.totalPages}
</div>
},
renderDummyPage : function(key){
return <div className='phb' key={key}>
<i className='fa fa-spinner fa-spin' />
</div>
},
renderPage : function(pageText, index){
return <div className='phb' dangerouslySetInnerHTML={{__html:Markdown(pageText)}} key={index} />
},
renderPages : function(){
var pages = this.props.text.split('\\page');
this.totalPages = pages.length;
return _.map(pages, (page, index)=>{
if(this.shouldRender(page, index)){
return this.renderPage(page, index);
}else{
return this.renderDummyPage(index);
}
});
},
render : function(){
return <div className='brewRenderer'
onScroll={this.handleScroll}
ref='main'
style={{height : this.state.height}}>
<div className='pages'>
{this.renderPages()}
</div>
{this.renderPageInfo()}
</div>
}
});
module.exports = BrewRenderer;

View File

@@ -0,0 +1,28 @@
@import (less) './client/homebrew/phbStyle/phb.style.less';
.pane{
position : relative;
}
.brewRenderer{
overflow-y : scroll;
.pageInfo{
position : absolute;
right : 17px;
bottom : 0;
z-index : 1000;
padding : 8px 10px;
background-color : #333;
font-size : 10px;
font-weight : 800;
color : white;
}
.pages{
margin : 30px 0px;
&>.phb{
margin-right : auto;
margin-bottom : 30px;
margin-left : auto;
box-shadow : 1px 4px 14px #000;
}
}
}

View File

@@ -1,86 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Statusbar = require('../statusbar/statusbar.jsx');
var PageContainer = require('../pageContainer/pageContainer.jsx');
var Editor = require('../editor/editor.jsx');
var FullClassGen = require('../editor/snippets/fullclass.gen.js');
var request = require("superagent");
var SAVE_TIMEOUT = 3000;
var EditPage = React.createClass({
getDefaultProps: function() {
return {
id : null,
entry : {
text : "",
shareId : null,
editId : null,
createdAt : null,
updatedAt : null,
}
};
},
getInitialState: function() {
return {
text: this.props.entry.text,
pending : false,
lastUpdated : this.props.entry.updatedAt
};
},
componentDidMount: function() {
var self = this;
window.onbeforeunload = function(){
if(!self.state.pending) return;
return "You have unsaved changes!";
}
},
handleTextChange : function(text){
this.setState({
text : text,
pending : true
});
this.save();
},
save : _.debounce(function(){
request
.put('/homebrew/update/' + this.props.id)
.send({text : this.state.text})
.end((err, res) => {
this.setState({
pending : false,
lastUpdated : res.body.updatedAt
})
})
}, SAVE_TIMEOUT),
render : function(){
return <div className='editPage'>
<Statusbar
editId={this.props.entry.editId}
shareId={this.props.entry.shareId}
printId={this.props.entry.shareId}
lastUpdated={this.state.lastUpdated}
isPending={this.state.pending} />
<div className='paneSplit'>
<div className='leftPane'>
<Editor text={this.state.text} onChange={this.handleTextChange} />
</div>
<div className='rightPane'>
<PageContainer text={this.state.text} />
</div>
</div>
</div>
}
});
module.exports = EditPage;

View File

@@ -1,5 +0,0 @@
.editPage{
}

View File

@@ -1,53 +1,83 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var SnippetIcons = require('./snippets/snippets.js');
var CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
var Snippets = require('./snippets/snippets.js');
var splice = function(str, index, inject){
return str.slice(0, index) + inject + str.slice(index);
};
var execute = function(val){
if(_.isFunction(val)) return val();
return val;
}
var Editor = React.createClass({
getDefaultProps: function() {
return {
text : "",
value : "",
onChange : function(){}
};
},
cursorPosition : {
line : 0,
ch : 0
},
componentDidMount: function() {
this.refs.textarea.focus();
var paneHeight = this.refs.main.parentNode.clientHeight;
paneHeight -= this.refs.snippetBar.clientHeight + 1;
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
},
handleTextChange : function(e){
this.props.onChange(e.target.value);
handleTextChange : function(text){
this.props.onChange(text);
},
handleCursorActivty : function(curpos){
this.cursorPosition = curpos;
},
iconClick : function(snippetFn){
var curPos = this.refs.textarea.selectionStart;
this.props.onChange(this.props.text.slice(0, curPos) +
snippetFn() +
this.props.text.slice(curPos + 1));
handleSnippetClick : function(injectText){
var lines = this.props.value.split('\n');
lines[this.cursorPosition.line] = splice(lines[this.cursorPosition.line], this.cursorPosition.ch, injectText);
this.handleTextChange(lines.join('\n'));
this.refs.codeEditor.setCursorPosition(this.cursorPosition.line, this.cursorPosition.ch + injectText.length);
},
renderTemplateIcons : function(){
return _.map(SnippetIcons, (t) => {
return <div className='icon' key={t.icon}
onClick={this.iconClick.bind(this, t.snippet)}
data-tooltip={t.tooltip}>
<i className={'fa ' + t.icon} />
</div>;
//Called when there are changes to the editor's dimensions
update : function(){
this.refs.codeEditor.updateSize();
},
renderSnippetGroups : function(){
return _.map(Snippets, (snippetGroup)=>{
return <SnippetGroup
groupName={snippetGroup.groupName}
icon={snippetGroup.icon}
snippets={snippetGroup.snippets}
key={snippetGroup.groupName}
onSnippetClick={this.handleSnippetClick}
/>
})
},
render : function(){
var self = this;
return(
<div className='editor'>
<div className='textIcons'>
{this.renderTemplateIcons()}
<div className='editor' ref='main'>
<div className='snippetBar' ref='snippetBar'>
{this.renderSnippetGroups()}
</div>
<textarea
ref='textarea'
value={this.props.text}
onChange={this.handleTextChange} />
<CodeEditor
ref='codeEditor'
wrap={true}
language='gfm'
value={this.props.value}
onChange={this.handleTextChange}
onCursorActivity={this.handleCursorActivty} />
</div>
);
}
@@ -55,3 +85,45 @@ var Editor = React.createClass({
module.exports = Editor;
var SnippetGroup = React.createClass({
getDefaultProps: function() {
return {
groupName : '',
icon : 'fa-rocket',
snippets : [],
onSnippetClick : function(){},
};
},
handleSnippetClick : function(snippet){
this.props.onSnippetClick(execute(snippet.gen));
},
renderSnippets : function(){
return _.map(this.props.snippets, (snippet)=>{
return <div className='snippet' key={snippet.name} onClick={this.handleSnippetClick.bind(this, snippet)}>
<i className={'fa fa-fw ' + snippet.icon} />
{snippet.name}
</div>
})
},
render : function(){
return <div className='snippetGroup'>
<div className='text'>
<i className={'fa fa-fw ' + this.props.icon} />
<span className='groupName'>{this.props.groupName}</span>
</div>
<div className='dropdown'>
{this.renderSnippets()}
</div>
</div>
},
});

View File

@@ -1,41 +1,56 @@
.editor{
position : relative;
height : 100%;
min-height : 100%;
width : 100%;
margin-top: 25px;
display: flex;
flex-direction: column;
.textIcons{
display : inline-block;
vertical-align : top;
.icon{
display : inline-block;
height : 30px;
width : 30px;
cursor : pointer;
font-size : 1.5em;
line-height : 30px;
text-align : center;
&:nth-child(8n + 1){ background-color: @blue; }
&:nth-child(8n + 2){ background-color: @orange; }
&:nth-child(8n + 3){ background-color: @teal; }
&:nth-child(8n + 4){ background-color: @red; }
&:nth-child(8n + 5){ background-color: @purple; }
&:nth-child(8n + 6){ background-color: @silver; }
&:nth-child(8n + 7){ background-color: @yellow; }
&:nth-child(8n + 8){ background-color: @green; }
position : relative;
width : 100%;
.snippetBar{
display : flex;
padding : 5px;
background-color : #ddd;
align-items : center;
.snippetGroup{
.animate(background-color);
margin : 0px 8px;
padding : 3px;
font-size : 13px;
border-radius : 5px;
&:hover, &.selected{
background-color : #999;
}
.text{
line-height : 20px;
.groupName{
margin-left : 6px;
font-size : 10px;
}
}
&:hover{
.dropdown{
visibility : visible;
}
}
.dropdown{
position : absolute;
visibility : hidden;
z-index : 1000;
padding : 5px;
background-color : #ddd;
.snippet{
.animate(background-color);
padding : 10px;
cursor : pointer;
font-size : 10px;
i{
margin-right: 8px;
font-size : 13px;
}
&:hover{
background-color : #999;
}
}
}
}
}
textarea{
box-sizing : border-box;
resize : none;
overflow-y : scroll;
height : 100%;
width : 100%;
padding : 10px;
border : none;
outline: none;
.codeEditor{
height : 100%;
}
}

View File

@@ -24,13 +24,13 @@ module.exports = function(classname){
"",
"#### Proficiencies",
"___",
"- **Armor:** " + (_.sample(["Light armor", "Medium armor", "Heavy armor", "Shields"], _.random(0,3)).join(', ') || "None"),
"- **Weapons:** " + (_.sample(["Squeegee", "Rubber Chicken", "Simple weapons", "Martial weapons"], _.random(0,2)).join(', ') || "None"),
"- **Tools:** " + (_.sample(["Artian's tools", "one musical instrument", "Thieve's tools"], _.random(0,2)).join(', ') || "None"),
"- **Armor:** " + (_.sampleSize(["Light armor", "Medium armor", "Heavy armor", "Shields"], _.random(0,3)).join(', ') || "None"),
"- **Weapons:** " + (_.sampleSize(["Squeegee", "Rubber Chicken", "Simple weapons", "Martial weapons"], _.random(0,2)).join(', ') || "None"),
"- **Tools:** " + (_.sampleSize(["Artian's tools", "one musical instrument", "Thieve's tools"], _.random(0,2)).join(', ') || "None"),
"",
"___",
"- **Saving Throws:** " + (_.sample(abilityList, 2).join(', ')),
"- **Skills:** Choose two from " + (_.sample(skillList, _.random(4, 6)).join(', ')),
"- **Saving Throws:** " + (_.sampleSize(abilityList, 2).join(', ')),
"- **Skills:** Choose two from " + (_.sampleSize(skillList, _.random(4, 6)).join(', ')),
"",
"#### Equipment",
"You start with the following equipment, in addition to the equipment granted by your background:",

View File

@@ -67,7 +67,7 @@ module.exports = {
var res = [
levelName,
"+" + Math.ceil(level/5 + 1),
_.sample(features, _.sample([0,1,1])).join(', ') || "Ability Score Improvement",
_.sampleSize(features, _.sample([0,1,1])).join(', ') || "Ability Score Improvement",
cantrips,
spells,
drawSlots(slots)
@@ -93,7 +93,7 @@ module.exports = {
var res = [
levelName,
"+" + Math.ceil(level/5 + 1),
_.sample(features, _.sample([0,1,1])).join(', ') || "Ability Score Improvement",
_.sampleSize(features, _.sample([0,1,1])).join(', ') || "Ability Score Improvement",
"+" + featureScore
].join(' | ');

View File

@@ -1,7 +1,7 @@
var _ = require('lodash');
var genList = function(list, max){
return _.sample(list, _.random(0,max)).join(', ') || "None";
return _.sampleSize(list, _.random(0,max)).join(', ') || "None";
}
var getMonsterName = function(){

View File

@@ -4,123 +4,172 @@ var MonsterBlockGen = require('./monsterblock.gen.js');
var ClassFeatureGen = require('./classfeature.gen.js');
var FullClassGen = require('./fullclass.gen.js');
module.exports = [
/*
{
tooltip : 'Full Class',
icon : 'fa-user',
snippet : FullClassGen,
},
*/
{
tooltip : 'Spell',
icon : 'fa-magic',
snippet : SpellGen,
},
{
tooltip : 'Class Feature',
icon : 'fa-trophy',
snippet : ClassFeatureGen,
},
{
tooltip : 'Note',
icon : 'fa-sticky-note',
snippet : function(){
return [
"> ##### Time to Drop Knowledge",
"> Use notes to point out some interesting information. ",
"> ",
"> **Tables and lists** both work within a note."
].join('\n');
},
},
{
tooltip : 'Table',
icon : 'fa-th-list',
snippet : function(){
return [
"##### Cookie Tastiness",
"| Tastiness | Cookie Type |",
"|:----:|:-------------|",
"| -5 | Raisin |",
"| 8th | Chocolate Chip |",
"| 11th | 2 or lower |",
"| 14th | 3 or lower |",
"| 17th | 4 or lower |\n\n",
].join('\n');
},
},
{
tooltip : 'Monster Stat Block',
icon : 'fa-bug',
snippet : MonsterBlockGen.half,
},
{
tooltip : 'Wide Monster Stat Block',
icon : 'fa-bullseye',
snippet : MonsterBlockGen.full,
},
{
tooltip : "Class Table",
icon : 'fa-table',
snippet : ClassTableGen.full,
},
{
tooltip : "Half Class Table",
icon : 'fa-list-alt',
snippet : ClassTableGen.half,
},
{
tooltip : "Column Break",
icon : 'fa-columns',
snippet : function(){
return "```\n```\n\n";
}
},
{
tooltip : "New Page",
icon : 'fa-file-text',
snippet : function(){
return "\\page\n\n";
}
},
{
tooltip : "Vertical Spacing",
icon : 'fa-arrows-v',
snippet : function(){
return "<div style='margin-top:140px'></div>\n\n";
}
},
{
tooltip : "Insert Image",
icon : 'fa-image',
snippet : function(){
return "<img src='https://i.imgur.com/RJ6S6eY.gif' style='position:absolute;bottom:-10px;right:-60px;' />";
}
},
{
tooltip : "Page number & Footnote",
groupName : 'Editor',
icon : 'fa-pencil',
snippets : [
{
name : "Column Break",
icon : 'fa-columns',
gen : "```\n```\n\n"
},
{
name : "New Page",
icon : 'fa-file-text',
gen : "\\page\n\n"
},
{
name : "Vertical Spacing",
icon : 'fa-arrows-v',
gen : "<div style='margin-top:140px'></div>\n\n"
},
{
name : "Image",
icon : 'fa-image',
gen : [
"<img ",
" src='https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg' ",
" style='width:325px' />",
"Credit: Kyounghwan Kim"
].join('\n')
},
{
name : "Background Image",
icon : 'fa-tree',
gen : [
"<img ",
" src='http://i.imgur.com/hMna6G0.png' ",
" style='position:absolute; top:50px; right:30px; width:280px' />"
].join('\n')
},
{
name : "Page Number",
icon : 'fa-bookmark',
gen : "<div class='pageNumber'>1</div>\n<div class='footnote'>PART 1 | FANCINESS</div>\n\n"
},
]
},
/************************* PHB ********************/
{
groupName : 'PHB',
icon : 'fa-book',
snippet : function(){
return "<div class='pageNumber'>1</div>\n<div class='footnote'>PART 1 | FANCINESS</div>\n\n";
}
snippets : [
{
name : 'Spell',
icon : 'fa-magic',
gen : SpellGen,
},
{
name : 'Class Feature',
icon : 'fa-trophy',
gen : ClassFeatureGen,
},
{
name : 'Note',
icon : 'fa-sticky-note',
gen : function(){
return [
"> ##### Time to Drop Knowledge",
"> Use notes to point out some interesting information. ",
"> ",
"> **Tables and lists** both work within a note."
].join('\n');
},
},
{
name : 'Monster Stat Block',
icon : 'fa-bug',
gen : MonsterBlockGen.half,
},
{
name : 'Wide Monster Stat Block',
icon : 'fa-paw',
gen : MonsterBlockGen.full,
}
]
},
/********************* TABLES *********************/
{
tooltip : "Ink Friendly",
groupName : 'Tables',
icon : 'fa-table',
snippets : [
{
name : "Class Table",
icon : 'fa-table',
gen : ClassTableGen.full,
},
{
name : "Half Class Table",
icon : 'fa-list-alt',
gen : ClassTableGen.half,
},
{
name : 'Table',
icon : 'fa-th-list',
gen : function(){
return [
"##### Cookie Tastiness",
"| Tastiness | Cookie Type |",
"|:----:|:-------------|",
"| -5 | Raisin |",
"| 8th | Chocolate Chip |",
"| 11th | 2 or lower |",
"| 14th | 3 or lower |",
"| 17th | 4 or lower |\n\n",
].join('\n');
},
}
]
},
/**************** PRINT *************/
{
groupName : 'Print',
icon : 'fa-print',
snippet : function(){
return "<style>\n .phb{ background : white;}\n .phb img{ display : none;}\n .phb hr+blockquote{background : white;}\n</style>\n\n";
}
snippets : [
{
name : "A4 PageSize",
icon : 'fa-file-o',
gen : ['<style>',
' .phb{',
' width : 210mm;',
' height : 296.8mm;',
' }',
'</style>'
].join('\n')
},
{
name : "Ink Friendly",
icon : 'fa-tint',
gen : ['<style>',
' .phb{ background : white;}',
' .phb img{ display : none;}',
' .phb hr+blockquote{background : white;}',
'</style>',
''
].join('\n')
},
]
},
{
tooltip : "A4 Page Size",
icon : 'fa-file',
snippet : function(){
return '<style>\n.phb{\n width : 210mm;\n height : 297mm;\n}\n</style>';
}
}
]
]

View File

@@ -56,9 +56,9 @@ module.exports = function(){
var spellSchools = ["abjuration", "conjuration", "divination", "enchantment", "evocation", "illusion", "necromancy", "transmutation"];
var components = _.sample(["V", "S", "M"], _.random(1,3)).join(', ');
var components = _.sampleSize(["V", "S", "M"], _.random(1,3)).join(', ');
if(components.indexOf("M") !== -1){
components += " (" + _.sample(['a small doll', 'a crushed button worth at least 1cp', 'discarded gum wrapper'], _.random(1,3)).join(', ') + ")"
components += " (" + _.sampleSize(['a small doll', 'a crushed button worth at least 1cp', 'discarded gum wrapper'], _.random(1,3)).join(', ') + ")"
}
return [

View File

@@ -1,71 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Statusbar = require('../statusbar/statusbar.jsx');
var PageContainer = require('../pageContainer/pageContainer.jsx');
var Editor = require('../editor/editor.jsx');
//var WelcomeText = require('./welcomeMessage.js');
var KEY = 'naturalCrit-homebrew';
var HomePage = React.createClass({
getDefaultProps: function() {
return {
welcomeText : ""
};
},
getInitialState: function() {
return {
text: this.props.welcomeText
};
},
componentDidMount: function() {
/*
var storage = localStorage.getItem(KEY);
if(storage){
this.setState({
text : storage
})
}
*/
},
handleTextChange : function(text){
this.setState({
text : text
});
//localStorage.setItem(KEY, text);
},
render : function(){
return(
<div className='homePage'>
<Statusbar
printId="Nkbh52nx_l"
/>
<div className='paneSplit'>
<div className='leftPane'>
<Editor text={this.state.text} onChange={this.handleTextChange} />
</div>
<div className='rightPane'>
<PageContainer text={this.state.text} />
</div>
</div>
<a href='/homebrew/new' className='floatingNewButton'>
Create your own <i className='fa fa-magic' />
</a>
</div>
);
}
});
module.exports = HomePage;

View File

@@ -4,9 +4,10 @@ var cx = require('classnames');
var CreateRouter = require('pico-router').createRouter;
var HomePage = require('./homePage/homePage.jsx');
var EditPage = require('./editPage/editPage.jsx');
var SharePage = require('./sharePage/sharePage.jsx');
var HomePage = require('./pages/homePage/homePage.jsx');
var EditPage = require('./pages/editPage/editPage.jsx');
var SharePage = require('./pages/sharePage/sharePage.jsx');
var NewPage = require('./pages/newPage/newPage.jsx');
var Router;
var Homebrew = React.createClass({
@@ -16,7 +17,8 @@ var Homebrew = React.createClass({
welcomeText : "",
changelog : "",
brew : {
text : "",
title : '',
text : '',
shareId : null,
editId : null,
createdAt : null,
@@ -27,14 +29,17 @@ var Homebrew = React.createClass({
componentWillMount: function() {
Router = CreateRouter({
'/homebrew/edit/:id' : (args) => {
return <EditPage id={args.id} entry={this.props.brew} />
return <EditPage id={args.id} brew={this.props.brew} />
},
'/homebrew/share/:id' : (args) => {
return <SharePage id={args.id} entry={this.props.brew} />
return <SharePage id={args.id} brew={this.props.brew} />
},
'/homebrew/changelog' : (args) => {
return <SharePage entry={{text : this.props.changelog}} />
return <SharePage brew={{title : 'Changelog', text : this.props.changelog}} />
},
'/homebrew/new' : (args) => {
return <NewPage />
},
'/homebrew*' : <HomePage welcomeText={this.props.welcomeText} />,
});

View File

@@ -1,45 +1,17 @@
@import 'naturalCrit/styles/reset.less';
//@import 'naturalCrit/styles/elements.less';
@import 'naturalCrit/styles/animations.less';
@import 'naturalCrit/styles/colors.less';
@import 'naturalCrit/styles/tooltip.less';
html,body, #reactContainer{
min-height: 100%;
font-family : 'Open Sans', sans-serif;
}
@import 'naturalcrit/styles/core.less';
.homebrew{
background-color: @steel;
height : 100%;
height : 100%;
.paneSplit{
width : 100%;
height: 100vh;
//padding-top: 25px;
position: relative;
box-sizing: border-box;
overflow: hidden;
.leftPane, .rightPane{
display: inline-block;
vertical-align: top;
position: relative;
height: 100%;
min-height: 100%;
//margin-top: 25px;
//TODO: Consider making backgroudn color lighter
background-color : @steel;
.page{
display : flex;
height : 100%;
flex-direction : column;
.content{
position : relative;
height : calc(~"100% - 29px"); //Navbar height
flex : auto;
}
.leftPane{
width : 40%;
}
.rightPane{
width : 60%;
height: 100%;
//overflow-y: scroll;
overflow: hidden;
}
}
}

View File

@@ -0,0 +1,33 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Nav = require('naturalcrit/nav/nav.jsx');
const MAX_TITLE_LENGTH = 50;
var EditTitle = React.createClass({
getDefaultProps: function() {
return {
title : '',
onChange : function(){}
};
},
handleChange : function(e){
if(e.target.value.length > MAX_TITLE_LENGTH) return;
this.props.onChange(e.target.value);
},
render : function(){
return <Nav.item className='editTitle'>
<input placeholder='Brew Title' type='text' value={this.props.title} onChange={this.handleChange} />
<div className={cx('charCount', {'max' : this.props.title.length >= MAX_TITLE_LENGTH})}>
{this.props.title.length}/{MAX_TITLE_LENGTH}
</div>
</Nav.item>
},
});
module.exports = EditTitle;

View File

@@ -0,0 +1,8 @@
var React = require('react');
var Nav = require('naturalcrit/nav/nav.jsx');
module.exports = function(props){
return <Nav.item newTab={true} href='https://github.com/stolksdorf/naturalcrit/issues' color='red' icon='fa-bug'>
report issue
</Nav.item>
};

View File

@@ -0,0 +1,21 @@
var React = require('react');
var _ = require('lodash');
var Nav = require('naturalcrit/nav/nav.jsx');
var Navbar = React.createClass({
render : function(){
return <Nav.base>
<Nav.section>
<Nav.logo />
<Nav.item href='/homebrew' className='homebrewLogo'>
<div>The Homebrewery</div>
</Nav.item>
<Nav.item>v2.0.0</Nav.item>
</Nav.section>
{this.props.children}
</Nav.base>
}
});
module.exports = Navbar;

View File

@@ -0,0 +1,49 @@
.homebrew nav{
.homebrewLogo{
.animate(color);
font-family : CodeBold;
font-size : 12px;
color : white;
div{
margin-top : 2px;
margin-bottom : -2px;
}
&:hover{
color : @blue;
}
}
.editTitle.navItem{
padding : 2px 12px;
input{
margin : 0;
padding : 2px;
width : 250px;
background-color : #444;
font-family : 'Open Sans', sans-serif;
font-size : 12px;
font-weight : 800;
color : white;
text-align : center;
border : 1px solid @blue;
outline : none;
}
.charCount{
display : inline-block;
vertical-align : bottom;
margin-left : 8px;
text-align : right;
color : #666;
&.max{
color : @red;
}
}
}
.brewTitle.navItem{
font-size : 12px;
font-weight : 800;
color : white;
text-align : center;
text-transform: initial;
}
}

View File

@@ -0,0 +1,8 @@
var React = require('react');
var Nav = require('naturalcrit/nav/nav.jsx');
module.exports = function(props){
return <Nav.item newTab={true} href='https://www.patreon.com/stolksdorf' color='green' icon='fa-gift'>
help out
</Nav.item>
};

View File

@@ -0,0 +1,8 @@
var React = require('react');
var Nav = require('naturalcrit/nav/nav.jsx');
module.exports = function(props){
return <Nav.item newTab={true} href={'/homebrew/print/' + props.shareId +'?dialog=true'} color='purple' icon='fa-print'>
print
</Nav.item>
};

View File

@@ -0,0 +1,51 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
//var striptags = require('striptags');
var Nav = require('naturalcrit/nav/nav.jsx');
const MAX_URL_SIZE = 2083;
const MAIN_URL = "https://www.reddit.com/r/UnearthedArcana/submit?selftext=true"
var RedditShare = React.createClass({
getDefaultProps: function() {
return {
brew : {
title : '',
sharedId : '',
text : ''
}
};
},
getText : function(){
},
handleClick : function(){
var url = [
MAIN_URL,
'title=' + encodeURIComponent(this.props.brew.title ? this.props.brew.title : 'Check out my brew!'),
'text=' + encodeURIComponent(this.props.brew.text)
].join('&');
window.open(url, '_blank');
},
render : function(){
return <Nav.item icon='fa-reddit-alien' color='red' onClick={this.handleClick}>
share on reddit
</Nav.item>
},
});
module.exports = RedditShare;

View File

@@ -1,53 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Markdown = require('marked');
var PAGE_HEIGHT = 1056 + 30;
var PageContainer = React.createClass({
getDefaultProps: function() {
return {
text : ""
};
},
getInitialState: function() {
return {
viewablePageIndex: 0
};
},
handleScroll : function(e){
this.setState({
viewablePageIndex : Math.floor(e.target.scrollTop / PAGE_HEIGHT)
});
},
renderDummyPage : function(key){
return <div className='phb' key={key}>
<i className='fa fa-spinner fa-spin' />
</div>
},
renderPages : function(){
var currentIndex = this.state.viewablePageIndex;
return _.map(this.props.text.split('\\page'), (pageText, index) => {
if(currentIndex - 1 == index || currentIndex == index || currentIndex + 1 == index){
return <div className='phb' dangerouslySetInnerHTML={{__html:Markdown(pageText)}} key={index} />
}else{
return this.renderDummyPage(index);
}
})
},
render : function(){
return <div className="pageContainer" onScroll={this.handleScroll}>
<div className='pages'>
{this.renderPages()}
</div>
</div>;
}
});
module.exports = PageContainer;

View File

@@ -1,19 +0,0 @@
@import (less) './client/homebrew/phbStyle/phb.style.less';
.pageContainer{
background-color : @steel;
margin-top: 25px;
overflow-y: scroll;
height : 100%;
.pages{
padding : 30px 0px;
&>.phb{
margin-right : auto;
margin-bottom : 30px;
margin-left : auto;
box-shadow : 1px 4px 14px #000;
}
}
}

View File

@@ -0,0 +1,172 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var request = require("superagent");
var Nav = require('naturalcrit/nav/nav.jsx');
var Navbar = require('../../navbar/navbar.jsx');
var EditTitle = require('../../navbar/editTitle.navitem.jsx');
var ReportIssue = require('../../navbar/issue.navitem.jsx');
var PrintLink = require('../../navbar/print.navitem.jsx');
var SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
var Editor = require('../../editor/editor.jsx');
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const SAVE_TIMEOUT = 3000;
var EditPage = React.createClass({
getDefaultProps: function() {
return {
id : null,
brew : {
title : '',
text : '',
shareId : null,
editId : null,
createdAt : null,
updatedAt : null,
}
};
},
getInitialState: function() {
return {
title : this.props.brew.title,
text: this.props.brew.text,
isSaving : false,
isPending : false,
errors : null,
lastUpdated : this.props.brew.updatedAt
};
},
savedBrew : null,
componentDidMount: function(){
this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
window.onbeforeunload = ()=>{
if(this.state.isSaving || this.state.isPending){
return 'You have unsaved changes!';
}
}
},
componentWillUnmount: function() {
window.onbeforeunload = function(){};
},
handleSplitMove : function(){
this.refs.editor.update();
},
handleTitleChange : function(title){
this.setState({
title : title,
isPending : true
});
(this.hasChanges() ? this.debounceSave() : this.debounceSave.cancel());
},
handleTextChange : function(text){
this.setState({
text : text,
isPending : true
});
(this.hasChanges() ? this.debounceSave() : this.debounceSave.cancel());
},
handleDelete : function(){
if(!confirm("are you sure you want to delete this brew?")) return;
if(!confirm("are you REALLY sure? You will not be able to recover it")) return;
request.get('/homebrew/api/remove/' + this.props.brew.editId)
.send()
.end(function(err, res){
window.location.href = '/homebrew';
});
},
hasChanges : function(){
if(this.savedBrew){
if(this.state.text !== this.savedBrew.text) return true;
if(this.state.title !== this.savedBrew.title) return true;
}else{
if(this.state.text !== this.props.brew.text) return true;
if(this.state.title !== this.props.brew.title) return true;
}
return false;
},
save : function(){
this.debounceSave.cancel();
this.setState({
isSaving : true
});
request
.put('/homebrew/api/update/' + this.props.brew.editId)
.send({
text : this.state.text,
title : this.state.title
})
.end((err, res) => {
this.savedBrew = res.body;
this.setState({
isPending : false,
isSaving : false,
lastUpdated : res.body.updatedAt
})
})
},
renderSaveButton : function(){
if(this.state.isSaving){
return <Nav.item className='save' icon="fa-spinner fa-spin">saving...</Nav.item>
}
if(!this.state.isPending && !this.state.isSaving){
return <Nav.item className='save saved'>saved.</Nav.item>
}
if(this.state.isPending && this.hasChanges()){
return <Nav.item className='save' onClick={this.save} color='blue' icon='fa-save'>Save Now</Nav.item>
}
},
renderNavbar : function(){
return <Navbar>
<Nav.section>
<EditTitle title={this.state.title} onChange={this.handleTitleChange} />
</Nav.section>
<Nav.section>
{this.renderSaveButton()}
<Nav.item newTab={true} href={'/homebrew/share/' + this.props.brew.shareId} color='teal' icon='fa-share-alt'>
Share
</Nav.item>
<PrintLink shareId={this.props.brew.shareId} />
<Nav.item color='red' icon='fa-trash' onClick={this.handleDelete}>
Delete
</Nav.item>
</Nav.section>
</Navbar>
},
render : function(){
return <div className='editPage page'>
{this.renderNavbar()}
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
<BrewRenderer text={this.state.text} />
</SplitPane>
</div>
</div>
}
});
module.exports = EditPage;

View File

@@ -0,0 +1,12 @@
.editPage{
.navItem.save{
width : 75px;
text-align: center;
&.saved{
color : #666;
cursor : initial;
}
}
}

View File

@@ -0,0 +1,70 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Nav = require('naturalcrit/nav/nav.jsx');
var Navbar = require('../../navbar/navbar.jsx');
var PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
var SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
var Editor = require('../../editor/editor.jsx');
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
var HomePage = React.createClass({
getDefaultProps: function() {
return {
welcomeText : ""
};
},
getInitialState: function() {
return {
text: this.props.welcomeText
};
},
handleSplitMove : function(){
this.refs.editor.update();
},
handleTextChange : function(text){
this.setState({
text : text
});
},
renderNavbar : function(){
return <Navbar>
<Nav.section>
<PatreonNavItem />
<Nav.item newTab={true} href='https://github.com/stolksdorf/naturalcrit/issues' color='red' icon='fa-bug'>
report issue
</Nav.item>
<Nav.item newTab={true} href='/homebrew/changelog' color='purple' icon='fa-file-text-o'>
Changelog
</Nav.item>
<Nav.item href='/homebrew/new' color='green' icon='fa-external-link'>
New Brew
</Nav.item>
</Nav.section>
</Navbar>
},
render : function(){
return <div className='homePage page'>
{this.renderNavbar()}
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
<BrewRenderer text={this.state.text} />
</SplitPane>
</div>
<a href='/homebrew/new' className='floatingNewButton'>
Create your own <i className='fa fa-magic' />
</a>
</div>
}
});
module.exports = HomePage;

View File

@@ -1,5 +1,7 @@
.homePage{
position : relative;
a.floatingNewButton{
.animate(background-color);

View File

@@ -2,9 +2,7 @@
Welcome traveler from an antique land. Please sit and tell us of what you have seen. The unheard of monsters, who slither and bite. Tell us of the wondrous items and and artifacts you have found, their mysteries yet to be unlocked. Of the vexing vocations and surprising skills you have seen.
### Homebrew D&D made easy
The Homebrewery allows for the creation and sharing of authentic looking Fifth-Edition homebrews, with just text editing. It accomplishes this by using [Markdown](https://help.github.com/articles/markdown-basics/) along with some custom CSS-styling.
Stop worrying about learning Photoshop, fiddling with spacing, or tracking down the PHB assets. Just focus on making your homebrew **great**.
The Homebrewery makes the creation and sharing of authentic looking Fifth-Edition homebrews easy. It uses [Markdown](https://help.github.com/articles/markdown-basics/) with a little CSS magic to make your brews come to life.
**Try it! **Simply edit the text on the left and watch it *update live* on the right.
@@ -16,49 +14,49 @@ Stop worrying about learning Photoshop, fiddling with spacing, or tracking down
* Full class tables
* Notes and Tables
* Images
* Page numbering and footers
* Vertical spacing, column breaks, and multiple pages
#### Snippets
If you aren't used the Markdown-style syntax, don't worry! I've provided several **snippets** at the top of the editor. When clicked, these will *inject* text wherever your text cursor was.
Each snippet is a common format from the Player's Handbook or is a feature of The Homebrewery. You'll never have to memorize exactly how a Monster Stat Block is suppose to be formatted.
### Editing and Sharing
When you create your own homebrew you will be given a *edit url* and a *share url*. Any changes you make will be automatically saved to the database within a few seconds. Anyone with the edit url will be able to make edits to your homebrew. So be careful about who you share it with.
Anyone with the *share url* will be able to access a read-only version of your homebrew.
## Helping out
Like this tool? Want to buy me a beer? [Head here](https://www.patreon.com/stolksdorf) to help me keep the servers running.
```
```
This tool will **always** be free, never have ads, and I will never offer any "premium" features or whatever.
## New Things in v1.4!
What's new in the latest update? Check out the full changelog [here](/homebrew/changelog)
* **Partial Page Rendering** Only renders the page you're looking at. If you have a very large brew, you'll love this.
* **Improved Zoom** Zooming in and out on the browser is now way nicer looking. Go get the bird's eye view!
>##### PDF Exporting
> Follow these steps to export your brew to PDF
> * Create a breath-taking homebrew
> * Share it with a few friends for feedback and balance
> * Install [Chrome Canary](https://www.google.com/chrome/browser/canary.html)
> * Go to your brew on Chrome Canary
> * Hit the **Print View** button
> * Print that page. Make sure the paper size is **letter**
> * You're done!
>
> Due to a bug in Chrome's Print To PDF feature, columns aren't supported. The fix to this has been released in Chrome Canary.
## Bugs, Issues, Suggestions?
### Bugs, Issues, Suggestions?
Have an idea of how to make The Homebrewery better? Or did you find something that wasn't quite right? Head [here](https://github.com/stolksdorf/NaturalCrit/issues/new) and let me know!.
You can also checkout the [Changelog here](/homebrew/changelog).
```
```
## New Things in v2.0.0!
What's new in the latest update? Check out the full changelog [here](/homebrew/changelog)
* **A whole new look** The site has been re-built from the ground up!
* **Better editor and Split Pane** Syntax highlighting will make writing your brews even easier, and now you can customize how large your editor is.
* **More reliable rendering** Lots of work has been put into making the rendering more reliable, not just for web, but also for PDFs
* **PDF Printing on Chrome** You don't need to use Chrome Canary anymore!
* ** Performance Improvements** The site should load faster, save faster, and render large brews *much* faster.
* **Patreon page** If you like this tool and want to show some thanks you can [head here](https://www.patreon.com/stolksdorf).
>##### PDF Exporting
> After clicking the "Print" item in the navbar a new page will open and a print dialog will pop-up
> * Set the **Destination** to "Save as PDF"
> * Set **Paper Size** to "Letter"
> * If you are printing on A4 paper, make sure to have the "A4 page size snippet" in your brew
> * In **Options** make sure "Background Images" is selected.
> * Hit print and enjoy! You're done!
>
> If you want to save ink or have a monochrome printer, add the **Ink Friendly** snippet to your brew before you print
@@ -89,7 +87,7 @@ ___
### Images
Images can be included 'inline' with the text using Markdown-style images. However for background images more control is needed.
Background images should be included as HTML-style img tags. Using inline CSS you can precisely position your image where you'd like it to be. The image **snippet** provides an example of doing this.
Background images should be included as HTML-style img tags. Using inline CSS you can precisely position your image where you'd like it to be. I have added both a inflow image snippet and a background image snippet to give you exmaples of how to do it.
```
```
@@ -98,13 +96,10 @@ Background images should be included as HTML-style img tags. Using inline CSS yo
### Legal Junk
You are free to use The Homebrewery is any way that you want, except for claiming that you made it yourself. If you wish to sell or in some way gain profit for what's created on this site, it's your responsibility to ensure you have the proper licenses/rights for any images or resources used.
### Crediting Me
If you'd like to credit The Homebrewery in your brew, I'd be flattered! Just reference that you made it with The Homebrewery.
### Things that don't work
There are a few things I couldn't get right
* Spell save block, with centered text and sans serif are not support. Ran out of mark-up to use
* "Spell slots per level" text above the levels on a class table.
* I built this for Chrome, so if it looks weird to you, use Chrome instead.
<div class='pageNumber'>2</div>
<div class='footnote'>PART 2 | BORING STUFF</div>

View File

@@ -0,0 +1,130 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var request = require("superagent");
var Nav = require('naturalcrit/nav/nav.jsx');
var Navbar = require('../../navbar/navbar.jsx');
var EditTitle = require('../../navbar/editTitle.navitem.jsx');
var SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
var Editor = require('../../editor/editor.jsx');
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const KEY = 'naturalCrit-homebrew-new';
var NewPage = React.createClass({
getInitialState: function() {
return {
title : 'My Awesome Brew v99',
text: '',
isSaving : false
};
},
componentDidMount: function() {
var storage = localStorage.getItem(KEY);
if(storage){
this.setState({
text : storage
})
}
window.onbeforeunload = (e)=>{
if(this.state.text == '') return;
return "Your homebrew isn't saved. Are you sure you want to leave?";
};
},
componentWillUnmount: function() {
window.onbeforeunload = function(){};
},
handleSplitMove : function(){
this.refs.editor.update();
},
handleTitleChange : function(title){
this.setState({
title : title
});
},
handleTextChange : function(text){
this.setState({
text : text
});
localStorage.setItem(KEY, text);
},
handleSave : function(){
this.setState({
isSaving : true
});
request.post('/homebrew/api')
.send({
title : this.state.title,
text : this.state.text
})
.end((err, res)=>{
if(err){
this.setState({
isSaving : false
});
return;
}
window.onbeforeunload = function(){};
var brew = res.body;
localStorage.removeItem(KEY);
window.location = '/homebrew/edit/' + brew.editId;
})
},
renderSaveButton : function(){
if(this.state.isSaving){
return <Nav.item icon='fa-spinner fa-spin' className='saveButton'>
save...
</Nav.item>
}else{
return <Nav.item icon='fa-save' className='saveButton' onClick={this.handleSave}>
save
</Nav.item>
}
},
renderNavbar : function(){
return <Navbar>
<Nav.section>
<EditTitle title={this.state.title} onChange={this.handleTitleChange} />
</Nav.section>
<Nav.section>
{this.renderSaveButton()}
<Nav.item newTab={true} href='https://github.com/stolksdorf/naturalcrit/issues' color='red' icon='fa-bug'>
report issue
</Nav.item>
</Nav.section>
</Navbar>
},
render : function(){
return <div className='newPage page'>
{this.renderNavbar()}
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
<BrewRenderer text={this.state.text} />
</SplitPane>
</div>
</div>
}
});
module.exports = NewPage;

View File

@@ -0,0 +1,10 @@
.newPage{
.saveButton{
background-color: @orange;
&:hover{
background-color: @green;
}
}
}

View File

@@ -0,0 +1,48 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Nav = require('naturalcrit/nav/nav.jsx');
var Navbar = require('../../navbar/navbar.jsx');
var PrintLink = require('../../navbar/print.navitem.jsx');
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
var SharePage = React.createClass({
getDefaultProps: function() {
return {
brew : {
title : '',
text : '',
shareId : null,
createdAt : null,
updatedAt : null,
views : 0
}
};
},
render : function(){
return <div className='sharePage page'>
<Navbar>
<Nav.section>
<Nav.item className='brewTitle'>{this.props.brew.title}</Nav.item>
</Nav.section>
<Nav.section>
<PrintLink shareId={this.props.brew.shareId} />
<Nav.item href={'/homebrew/source/' + this.props.brew.shareId} color='teal' icon='fa-code'>
source
</Nav.item>
</Nav.section>
</Navbar>
<div className='content'>
<BrewRenderer text={this.props.brew.text} />
</div>
</div>
}
});
module.exports = SharePage;

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
@import (less) 'shared/naturalCrit/styles/reset.less';
@import (less) 'shared/naturalcrit/styles/reset.less';
@import (less) './client/homebrew/phbStyle/phb.fonts.css';
@import (less) './client/homebrew/phbStyle/phb.assets.less';
//Colors
@@ -65,14 +65,16 @@
ul{
margin-bottom : 0.8em;
line-height : 1.3em;
list-style-position : inside;
list-style-position : outside;
list-style-type : disc;
padding-left: 1.4em;
}
ol{
margin-bottom : 0.8em;
line-height : 1.3em;
list-style-position : inside;
list-style-position : outside;
list-style-type : decimal;
padding-left: 1.4em;
}
img{
z-index : -1;
@@ -85,16 +87,15 @@
font-style : italic;
}
sup{
vertical-align: super;
font-size: smaller;
line-height: 0;
vertical-align : super;
font-size : smaller;
line-height : 0;
}
sub{
vertical-align: sub;
font-size: smaller;
line-height: 0;
vertical-align : sub;
font-size : smaller;
line-height : 0;
}
//*****************************
// * HEADERS
// *****************************/
@@ -172,24 +173,32 @@
margin-bottom : 1em;
padding : 5px 10px;
background-color : @noteGreen;
border-top : 2px black solid;
border-bottom : 2px black solid;
border-style: solid;
border-width: 11px;
border-image: @noteBorderImage 11;
border-image-outset: 9px 0px;
box-shadow : 1px 4px 14px #888;
p, ul{
font-size : 0.352cm;
font-size : 0.352cm;
line-height : 1.1em;
}
}
//If a note starts a column, give it space at the top to render border
pre+blockquote{
margin-top: 11px;
}
//*****************************
// * MONSTER STAT BLOCK
// *****************************/
hr+blockquote{
position : relative;
padding-top : 15px;
background-color : @monsterStatBackground;
border : none;
padding-top: 15px;
border-style : solid;
border-width : 10px;
border-image : @monsterBorderImage 10;
h2{
margin-top: -8px;
margin-top : -8px;
margin-bottom : 0px;
&+p{
padding-bottom : 0px;
@@ -203,7 +212,7 @@
ul{
.useSansSerif();
padding-left : 1em;
font-size : 0.352cm;
font-size : 0.352cm;
color : @headerText;
text-indent : -1em;
list-style-type : none;
@@ -214,6 +223,7 @@
column-span : 1;
background-color : transparent;
border-image : none;
border-style : none;
-webkit-column-span : 1;
tbody{
tr:nth-child(odd), tr:nth-child(even){
@@ -231,44 +241,12 @@
}
//Triangle dividers
hr{
@height : 3px;
position : relative;
visibility : visible;
margin : 8px 0px;
border-color : transparent;
&:after, &:before{
content : "";
position : absolute;
left : 0px;
height : @height;
width : 100%;
}
&:before{
top : -@height;
background : linear-gradient(to right top, @horizontalRule 40%, transparent 50%)
}
&:after{
top : 0px;
background : linear-gradient(to right bottom, @horizontalRule 40%, transparent 50%)
}
}
//Top and Bottom Borders
&:after, &:before{
content : "";
position : absolute;
height : 4px;
width : 100%;
padding : 0px 3px;
background-color : #E69A28;
border : 1px solid black;
}
&:before{
top : 0px;
left : -3px;
}
&:after{
bottom : 0px;
left : -3px;
visibility : visible;
height : 6px;
margin : 4px 0px;
background-image : @redTriangleImage;
background-size : 100% 100%;
border : none;
}
}
//Full Width
@@ -285,6 +263,7 @@
border-collapse : separate;
background-color : white;
border : initial;
border-style : solid;
border-image-outset : 37px 17px;
border-image-repeat : round;
border-image-slice : 150 200 150 200;
@@ -296,8 +275,6 @@
-webkit-column-span : all;
-moz-column-span : all;
}
//*****************************
// * FOOTER
// *****************************/
@@ -385,7 +362,15 @@
}
// Nested lists
ul ul,ol ol,ul ol,ol ul{
margin-left: 1.5em;
margin-bottom : 0px;
margin-left : 1.5em;
}
}
//*****************************
// * PRINT
// *****************************/
.phb.print{
blockquote{
box-shadow : none;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

View File

@@ -1,41 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Statusbar = require('../statusbar/statusbar.jsx');
var PageContainer = require('../pageContainer/pageContainer.jsx');
var SharePage = React.createClass({
getDefaultProps: function() {
return {
id : null,
entry : {
text : "",
shareId : null,
editId : null,
createdAt : null,
updatedAt : null,
views : 0
}
};
},
render : function(){
return(
<div className='sharePage'>
<Statusbar
sourceText={this.props.entry.text}
lastUpdated={this.props.entry.updatedAt}
views={this.props.entry.views}
printId={this.props.entry.shareId}
shareId={this.props.entry.shareId}
/>
<PageContainer text={this.props.entry.text} />
</div>
);
}
});
module.exports = SharePage;

View File

@@ -1,173 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Moment = require('moment');
var request = require('superagent')
var Logo = require('naturalCrit/logo/logo.jsx');
var replaceAll = function(str, find, replace) {
return str.replace(new RegExp(find, 'g'), replace);
}
var Statusbar = React.createClass({
getDefaultProps: function() {
return {
editId: null,
sourceText : null,
shareId : null,
printId : null,
isPending : false,
lastUpdated : null,
info : null,
views : 0
};
},
componentDidMount: function() {
//Updates the last updated text every 10 seconds
if(this.props.lastUpdated){
this.refreshTimer = setInterval(()=>{
this.forceUpdate();
}, 10000)
}
},
componentWillUnmount: function() {
clearInterval(this.refreshTimer);
},
deleteBrew : function(){
if(!confirm("are you sure you want to delete this brew?")) return;
if(!confirm("are you REALLY sure? You will not be able to recover it")) return;
request.get('/homebrew/api/remove/' + this.props.editId)
.send()
.end(function(err, res){
window.location.href = '/homebrew';
});
},
openSourceWindow : function(){
var sourceWindow = window.open();
var content = replaceAll(this.props.sourceText, '<', '&lt;');
content = replaceAll(content, '>', '&gt;');
sourceWindow.document.write('<code><pre>' + content + '</pre></code>');
},
renderInfo : function(){
if(!this.props.lastUpdated) return null;
return [
<div className='views' key='views'>
Views: {this.props.views}
</div>,
<div className='lastUpdated' key='lastUpdated'>
Last updated: {Moment(this.props.lastUpdated).fromNow()}
</div>
];
},
renderChromeTip : function(){
if(typeof window !== 'undefined' && window.chrome) return;
return <div
className='chromeField'
data-tooltip="If you are noticing rendering issues, try using Chrome instead.">
<i className='fa fa-exclamation-triangle' />
Optimized for Chrome
</div>
},
renderSourceButton : function(){
if(!this.props.sourceText) return null;
return <a className='sourceField' onClick={this.openSourceWindow}>
View Source <i className='fa fa-code' />
</a>
},
renderNewButton : function(){
if(this.props.editId || this.props.shareId) return null;
return <a className='newButton' target='_blank' href='/homebrew/new'>
New Brew <i className='fa fa-external-link' />
</a>
},
renderChangelogButton : function(){
if(this.props.editId || this.props.shareId) return null;
return <a className='changelogButton' target='_blank' href='/homebrew/changelog'>
Changelog <i className='fa fa-file-text-o' />
</a>
},
renderShare : function(){
if(!this.props.shareId) return null;
return <a className='shareField' key='share' href={'/homebrew/share/' + this.props.shareId} target="_blank">
Share Link <i className='fa fa-external-link' />
</a>
},
renderPrintButton : function(){
if(!this.props.printId) return null;
return <a className='printField' key='print' href={'/homebrew/print/' + this.props.printId} target="_blank">
Print View <i className='fa fa-print' />
</a>
},
renderDeleteButton : function(){
if(!this.props.editId) return null;
return <div className='deleteButton' onClick={this.deleteBrew}>
Delete <i className='fa fa-trash' />
</div>
},
renderStatus : function(){
if(!this.props.editId) return null;
var text = 'Saved.'
if(this.props.isPending){
text = 'Saving...'
}
return <div className='savingStatus'>
{text}
</div>
},
render : function(){
return <div className='statusbar'>
<Logo
hoverSlide={true}
/>
<div className='left'>
<a href='/homebrew' className='toolName'>
The Home<small>Brewery</small>
</a>
</div>
<div className='controls right'>
{this.renderChromeTip()}
{this.renderChangelogButton()}
{this.renderStatus()}
{this.renderInfo()}
{this.renderSourceButton()}
{this.renderDeleteButton()}
{this.renderPrintButton()}
{this.renderShare()}
{this.renderNewButton()}
</div>
</div>
}
});
module.exports = Statusbar;

View File

@@ -1,135 +0,0 @@
.statusbar{
position : fixed;
z-index : 1000;
height : 25px;
width : 100%;
background-color : black;
font-size : 24px;
color : white;
line-height : 1.0em;
border-bottom : 1px solid @grey;
.logo{
display : inline-block;
vertical-align : middle;
margin-top : -5px;
margin-right : 20px;
svg{
margin-top : -6px;
}
}
.left{
display : inline-block;
vertical-align : top;
}
.right{
float : right;
}
.toolName{
display : block;
vertical-align : middle;
font-family : CodeBold;
font-size : 16px;
color : white;
line-height : 30px;
text-decoration : none;
small{
font-family : CodeBold;
}
}
.controls{
font-size : 12px;
>*{
display : inline-block;
height : 100%;
padding : 0px 10px;
border-left : 1px solid @grey;
}
.savingStatus{
width : 56px;
color : @grey;
text-align : center;
}
.newButton{
.animate(background-color);
color : white;
text-decoration : none;
&:hover{
background-color : fade(@green, 70%);
}
}
.chromeField{
background-color: @orange;
color : white;
text-decoration : none;
i{
margin-right: 10px;
}
}
.changelogButton{
.animate(background-color);
color : white;
text-decoration : none;
&:hover{
background-color : fade(@purple, 70%);
}
}
.deleteButton{
.animate(background-color);
color : white;
text-decoration : none;
cursor: pointer;
&:hover{
background-color : fade(@red, 70%);
}
}
.shareField{
.animate(background-color);
cursor : pointer;
color : white;
text-decoration : none;
&:hover{
background-color : fade(@teal, 70%);
}
span{
margin-right : 5px;
}
input{
width : 100px;
font-size : 12px;
}
}
.printField{
.animate(background-color);
cursor : pointer;
color : white;
text-decoration : none;
&:hover{
background-color : fade(@orange, 70%);
}
span{
margin-right : 5px;
}
input{
width : 100px;
font-size : 12px;
}
}
.sourceField{
.animate(background-color);
cursor : pointer;
color : white;
text-decoration : none;
&:hover{
background-color : fade(@teal, 70%);
}
span{
margin-right : 5px;
}
input{
width : 100px;
font-size : 12px;
}
}
}
}

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

86
client/main/main.jsx Normal file
View File

@@ -0,0 +1,86 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Router = require('pico-router');
var NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
var HomebrewIcon = require('naturalcrit/svg/homebrew.svg.jsx');
var Main = React.createClass({
getDefaultProps: function() {
return {
tools : [
{
id : 'homebrew',
path : '/homebrew',
name : 'The Homebrewery',
icon : <HomebrewIcon />,
desc : 'Make authentic-looking 5e homebrews using Markdown',
show : true,
beta : false
},
{
id : 'homebrew2',
path : '/homebrew',
name : 'The Homebrewery',
icon : <HomebrewIcon />,
desc : 'Make authentic-looking 5e homebrews using Markdown',
show : false,
beta : true
},
{
id : 'homebrewfg2',
path : '/homebrew',
name : 'The Homebrewery',
icon : <HomebrewIcon />,
desc : 'Make authentic-looking 5e homebrews using Markdown',
show : false,
beta : false
}
]
};
},
renderTool : function(tool){
if(!tool.show) return null;
return <a href={tool.path} className={cx('tool', tool.id, {beta : tool.beta})} key={tool.id}>
<div className='content'>
{tool.icon}
<h2>{tool.name}</h2>
<p>{tool.desc}</p>
</div>
</a>;
},
renderTools : function(){
return _.map(this.props.tools, (tool)=>{
return this.renderTool(tool);
});
},
render : function(){
return <div className='main'>
<div className='top'>
<div className='logo'>
<NaturalCritIcon />
<span className='name'>
Natural
<span className='crit'>Crit</span>
</span>
</div>
<p>Top-tier tools for the discerning DM</p>
</div>
<div className='tools'>
{this.renderTools()}
</div>
</div>
}
});
module.exports = Main;

View File

@@ -1,5 +1,5 @@
.home{
@import 'naturalcrit/styles/core.less';
.main{
height : 100vh;
background-color : white;
.top{
@@ -12,7 +12,16 @@
font-size : 4em;
color : black;
svg{
fill : black;
height : .9em;
margin-right : .2em;
cursor : pointer;
fill : black;
}
.name{
font-family : 'CodeLight';
.crit{
font-family : 'CodeBold';
}
}
}
p{
@@ -25,24 +34,25 @@
.tools{
width : 100%;
text-align : center;
.toolContainer{
.tool{
.sequentialDelay(0.5s, 1s);
.fadeInDown(1s);
.keep();
display : inline-block;
cursor : pointer;
opacity : 0;
text-align : center;
border-right : 1px solid #333;
&:last-child{
border : none;
display : inline-block;
cursor : pointer;
opacity : 0;
color : black;
text-align : center;
text-decoration : none;
&+.tool{
border-left : 1px solid #666;
}
.content{
.addSketch(360px);
.animateAll(0.5s);
position : relative;
width : 500px;
padding : 40px;
width : 320px;
padding : 35px;
&:hover{
svg, h2{
.transform(scale(1.3));
@@ -63,19 +73,11 @@
height : 10em;
}
}
//Proejct specific styles
&.homebrew{
.content:hover{
background-color : fade(@teal, 20%);
}
.content:hover{
background-color : fade(@teal, 20%);
}
&.combat{
.content:hover{
background-color : fade(@red, 20%);
}
}
//Under Construction styles
&.underConstruction{
//Beta styles
&.beta{
cursor : initial;
.content{
&:hover{
@@ -88,7 +90,7 @@
}
&:after{
.animateAll();
content : "Under Construction";
content : "beta!";
position : absolute;
display : block;
top : 120px;
@@ -102,21 +104,6 @@
text-align : center;
text-transform : uppercase;
}
&:before{
.rumble(6s);
content : "";
position : absolute;
display : block;
top : 130px;
right : 30px;
height : 50px;
width : 40px;
//opacity : 0;
background-image : url('/assets/naturalCrit/home/bulldozer.png');
background-repeat : no-repeat;
background-size : contain;
animation-iteration-count : infinite;
}
}
}
}
@@ -146,21 +133,4 @@
@-ms-keyframes sketch {.sketchKeyFrames();}
@-o-keyframes sketch {.sketchKeyFrames();}
@keyframes sketch {.sketchKeyFrames();}
}
/*
.sketch(@length, @color : black, @duration : 3s, @easing : @defaultEasing){
.createAnimation(bounce, @duration, @easing);
.sketchKeyFrames(){
0% { stroke-dashoffset : 0px; fill:@color;}
15% { stroke-dashoffset : 0px; fill : transparent}
50% { stroke-dashoffset : @length; fill: transparent}
85% { stroke-dashoffset : 0px; fill:transparent;}
100% { stroke-dashoffset : 0px; fill:@color;}
}
@-webkit-keyframes bounce {.sketchKeyFrames();}
@-moz-keyframes bounce {.sketchKeyFrames();}
@-ms-keyframes bounce {.sketchKeyFrames();}
@-o-keyframes bounce {.sketchKeyFrames();}
@keyframes bounce {.sketchKeyFrames();}
}
*/
}

View File

@@ -1,165 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Sidebar = require('./sidebar/sidebar.jsx');
var Encounter = require('./encounter/encounter.jsx');
var encounters = [
{
name : 'The Big Bad',
desc : 'The big fight!',
reward : 'gems',
enemies : ['goblin', 'goblin'],
reserve : ['goblin'],
},
{
name : 'Demon Goats',
desc : 'Gross fight',
reward : 'curved horn, goat sac',
enemies : ['demon_goat', 'demon_goat', 'demon_goat'],
unique : {
demon_goat : {
"hp" : 140,
"ac" : 16,
"attr" : {
"str" : 8,
"con" : 8,
"dex" : 8,
"int" : 8,
"wis" : 8,
"cha" : 8
},
"attacks" : {
"charge" : {
"atk" : "1d20+5",
"dmg" : "1d8+5",
"type" : "bludge"
}
},
"abilities" : ["charge"],
}
}
},
];
var defaultMonsterManual = require('naturalCrit/defaultMonsterManual.js');
var attrMod = function(attr){
return Math.floor(attr/2) - 5;
}
var Store = require('naturalCrit/combat.store');
var Actions = require('naturalCrit/combat.actions');
var CombatManager = React.createClass({
mixins : [Store.mixin()],
getInitialState: function() {
var self = this;
return {
selectedEncounterIndex : 0,
encounters : JSON.parse(localStorage.getItem('encounters')) || encounters,
monsterManual : JSON.parse(localStorage.getItem('monsterManual')) || defaultMonsterManual,
players : localStorage.getItem('players') || 'jasper 13\nzatch 19',
};
},
onStoreChange : function(){
console.log('STORE CAHNGE', Store.getInc());
this.setState({
inc : Store.getInc()
})
},
handleEncounterJSONChange : function(encounterIndex, json){
this.state.encounters[encounterIndex] = json;
this.setState({
encounters : this.state.encounters
})
localStorage.setItem("encounters", JSON.stringify(this.state.encounters));
},
handleMonsterManualJSONChange : function(json){
this.setState({
monsterManual : json
});
localStorage.setItem("monsterManual", JSON.stringify(this.state.monsterManual));
},
handlePlayerChange : function(e){
this.setState({
players : e.target.value
});
localStorage.setItem("players", e.target.value);
},
handleSelectedEncounterChange : function(encounterIndex){
console.log(encounterIndex);
this.setState({
selectedEncounterIndex : encounterIndex
});
},
handleRemoveEncounter : function(encounterIndex){
this.state.encounters.splice(encounterIndex, 1);
this.setState({
encounters : this.state.encounters
});
localStorage.setItem("encounters", JSON.stringify(this.state.encounters));
},
renderSelectedEncounter : function(){
var self = this;
if(this.state.selectedEncounterIndex != null && this.state.encounters[this.state.selectedEncounterIndex]){
var selectedEncounter = this.state.encounters[this.state.selectedEncounterIndex]
return <Encounter
key={selectedEncounter.name}
{...selectedEncounter}
monsterManual={this.state.monsterManual}
players={this.state.players}
/>
}
return null;
},
temp : function(){
Actions.setInc(++this.state.inc);
},
render : function(){
var self = this;
return(
<div className='combatManager'>
<Sidebar
selectedEncounter={this.state.selectedEncounterIndex}
encounters={this.state.encounters}
monsterManual={this.state.monsterManual}
players={this.state.players}
onSelectEncounter={this.handleSelectedEncounterChange}
onRemoveEncounter={this.handleRemoveEncounter}
onJSONChange={this.handleEncounterJSONChange}
onMonsterManualChange={this.handleMonsterManualJSONChange}
onPlayerChange={this.handlePlayerChange}
/>
{this.renderSelectedEncounter()}
<button onClick={this.temp}>YUP {this.state.inc}</button>
</div>
);
}
});
module.exports = CombatManager;

View File

@@ -1,8 +0,0 @@
.combatManager{
.encounterContainer{
display: inline-block;
vertical-align: top;
}
}

View File

@@ -1,162 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Store = require('naturalCrit/combat.store.js');
var MonsterCard = require('./monsterCard/monsterCard.jsx');
var attrMod = function(attr){
return Math.floor(attr/2) - 5;
}
var Encounter = React.createClass({
mixins : [Store.mixin()],
getInitialState: function() {
return {
enemies: this.createEnemies(this.props)
};
},
onStoreChange : function(){
var players = Store.getplayersText();
},
getDefaultProps: function() {
return {
name : '',
desc : '',
reward : '',
enemies : [],
players : '',
unique : {},
monsterManual : {}
};
},
componentWillReceiveProps: function(nextProps) {
this.setState({
enemies : this.createEnemies(nextProps)
})
},
createEnemies : function(props){
var self = this;
return _.indexBy(_.map(props.enemies, function(type, index){
return self.createEnemy(props, type, index)
}), 'id')
},
createEnemy : function(props, type, index){
var stats = props.unique[type] || props.monsterManual[type];
if(!stats) return;
return _.extend({
id : type + index,
name : type,
currentHP : stats.hp,
initiative : _.random(1,20) + attrMod(stats.attr.dex)
}, stats);
},
updateHP : function(enemyId, newHP){
this.state.enemies[enemyId].currentHP = newHP;
this.setState({
enemies : this.state.enemies
});
},
removeEnemy : function(enemyId){
delete this.state.enemies[enemyId];
this.setState({
enemies : this.state.enemies
});
},
getPlayerObjects : function(){
return _.reduce(this.props.players.split('\n'), function(r, line){
var parts = line.split(' ');
if(parts.length != 2) return r;
r.push({
name : parts[0],
initiative : parts[1] * 1,
isPC : true
})
return r;
},[])
},
renderEnemies : function(){
var self = this;
var sortedEnemies = _.sortBy(_.union(_.values(this.state.enemies), this.getPlayerObjects()), function(e){
if(e && e.initiative) return -e.initiative;
return 0;
});
return _.map(sortedEnemies, function(enemy){
if(enemy.isPC){
return <PlayerCard {...enemy} key={enemy.name} />
}
return <MonsterCard
{...enemy}
key={enemy.id}
updateHP={self.updateHP.bind(self, enemy.id)}
remove={self.removeEnemy.bind(self, enemy.id)}
/>
})
},
render : function(){
var self = this;
var reward;
if(this.props.reward){
reward = <div className='reward'>
<i className='fa fa-trophy' /> Rewards: {this.props.reward}
</div>
}
return(
<div className='mainEncounter'>
<div className='info'>
<h1>{this.props.name}</h1>
<p>{this.props.desc}</p>
{reward}
</div>
<div className='cardContainer'>
{this.renderEnemies()}
</div>
</div>
);
}
});
module.exports = Encounter;
var PlayerCard = React.createClass({
getDefaultProps: function() {
return {
name : '',
initiative : 0
};
},
render : function(){
return <div className='playerCard'>
<span className='name'>{_.startCase(this.props.name)}</span>
<span className='initiative'><i className='fa fa-hourglass-2'/>{this.props.initiative}</span>
</div>
},
})

View File

@@ -1,36 +0,0 @@
.mainEncounter{
box-sizing : border-box;
overflow : hidden;
width : auto;
&>.info{
margin-left: 10px;
padding-bottom : 10px;
border-bottom: 1px solid #ddd;
h1{
font-size: 2em;
font-weight: 800;
margin-bottom: 5px;
}
p{
margin-left: 10px;
font-size: 0.8em;
line-height: 1.5em;
max-width: 600px;
}
.reward{
font-size: 0.8em;
font-weight: 800;
margin-top: 5px;
i{
margin-right: 5px;
}
}
}
}

View File

@@ -1,101 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var RollDice = require('naturalCrit/rollDice');
var AttackSlot = React.createClass({
getDefaultProps: function() {
return {
name : '',
uses : null
};
},
getInitialState: function() {
return {
lastRoll: {},
usedCount : 0
};
},
rollDice : function(key, notation){
var res = RollDice(notation);
this.state.lastRoll[key] = res
this.state.lastRoll[key + 'key'] = _.uniqueId(key);
this.setState({
lastRoll : this.state.lastRoll
})
},
renderUses : function(){
var self = this;
if(!this.props.uses) return null;
return _.times(this.props.uses, function(index){
var atCount = index < self.state.usedCount;
return <i
key={index}
className={cx('fa', {'fa-circle-o' : !atCount, 'fa-circle' : atCount})}
onClick={self.updateCount.bind(self, atCount)}
/>
})
},
updateCount : function(used){
this.setState({
usedCount : this.state.usedCount + (used ? -1 : 1)
});
},
renderNotes : function(){
var notes = _.omit(this.props, ['name', 'atk', 'dmg', 'uses', 'heal']);
return _.map(notes, function(text, key){
return <div key={key}>{key + ': ' + text}</div>
});
},
renderRolls : function(){
var self = this;
return _.map(['atk', 'dmg', 'heal'], function(type){
if(!self.props[type]) return null;
return <div className={cx('roll', type)} key={type}>
<button onClick={self.rollDice.bind(self, type, self.props[type])}>
<i className={cx('fa', {
'fa-hand-grab-o' : type=='dmg',
'fa-bullseye' : type=='atk',
'fa-plus' : type=='heal'
})} />
{self.props[type]}
</button>
<span key={self.state.lastRoll[type+'key']}>{self.state.lastRoll[type]}</span>
</div>
})
},
render : function(){
var self = this;
return(
<div className='attackSlot'>
<div className='info'>
<div className='name'>{this.props.name}</div>
<div className='uses'>
{this.renderUses()}
</div>
<div className='notes'>
{this.renderNotes()}
</div>
</div>
<div className='rolls'>
{this.renderRolls()}
</div>
</div>
);
}
});
module.exports = AttackSlot;

View File

@@ -1,71 +0,0 @@
.attackSlot{
//border : 1px solid black;
border-bottom: 1px solid #eee;
margin-bottom : 5px;
font-size : 0.8em;
.info, .rolls{
display : inline-block;
vertical-align : top;
}
.info{
width : 40%;
.name{
font-weight : 800;
margin-bottom: 3px;
}
.notes{
font-size : 0.8em;
}
.uses{
cursor : pointer;
//font-size: 0.8em;
//margin-top: 3px;
}
}
.rolls{
.roll{
margin-bottom : 2px;
&>span{
font-weight: 800;
.fadeInLeft();
}
button{
width : 70px;
margin-right : 5px;
cursor : pointer;
font-size : 0.7em;
font-weight : 800;
text-align : left;
border : none;
outline : 0;
i{
width : 15px;
margin-right : 5px;
border-right : 1px solid white;
}
&:hover{
//text-align: right;
}
}
&.atk{
button{
background-color : fade(@blue, 40%);
i { border-color: @blue}
}
}
&.dmg{
button{
background-color : fade(@red, 40%);
i { border-color: @red}
}
}
&.heal{
button{
background-color : fade(@green, 40%);
i { border-color: @green}
}
}
}
}
}

View File

@@ -1,202 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var AttackSlot = require('./attackSlot/attackSlot.jsx');
var MonsterCard = React.createClass({
getDefaultProps: function() {
return {
name : '',
hp : 1,
currentHP : 1,
ac: 1,
move : 30,
attr : {
str : 8,
con : 8,
dex : 8,
int : 8,
wis : 8,
cha : 8
},
attacks : {},
spells : {},
abilities : [],
items : [],
updateHP : function(){},
remove : function(){},
};
},
getInitialState: function() {
return {
status : 'normal',
usedItems : [],
lastRoll : { },
mousePos : null,
tempHP : 0
};
},
componentDidMount: function() {
window.addEventListener('mousemove', this.handleMouseDrag);
window.addEventListener('mouseup', this.handleMouseUp);
},
handleMouseDown : function(e){
this.setState({
mousePos : {
x : e.pageX,
y : e.pageY,
}
});
e.stopPropagation()
e.preventDefault()
},
handleMouseUp : function(e){
if(!this.state.mousePos) return;
this.props.updateHP(this.props.currentHP + this.state.tempHP);
this.setState({
mousePos : null,
tempHP : 0
});
},
handleMouseDrag : function(e){
if (!this.state.mousePos) return;
var distance = Math.sqrt(Math.pow(e.pageX - this.state.mousePos.x, 2) + Math.pow(e.pageY - this.state.mousePos.y, 2));
var mult = (e.pageY > this.state.mousePos.y ? -1 : 1)
this.setState({
tempHP : Math.floor(distance * mult/25)
})
},
addUsed : function(item, shouldRemove){
if(!shouldRemove) this.state.usedItems.push(item);
if(shouldRemove) this.state.usedItems.splice(this.state.usedItems.indexOf(item), 1);
this.setState({
usedItems : this.state.usedItems
});
},
renderHPBox : function(){
var self = this;
var tempHP
if(this.state.tempHP){
var sign = (this.state.tempHP > 0 ? '+' : '');
tempHP = <span className='tempHP'>{['(',sign,this.state.tempHP,')'].join('')}</span>
}
return <div className='hpBox' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
<div className='currentHP'>
{tempHP} {this.props.currentHP}
</div>
{self.renderStats()}
</div>
},
renderStats : function(){
var stats = {
'fa fa-shield' : this.props.ac,
//'fa fa-hourglass-2' : this.props.initiative,
}
return _.map(stats, function(val, icon){
return <div className='stat' key={icon}> {val} <i className={icon} /></div>
})
},
renderAttacks : function(){
var self = this;
return _.map(this.props.attacks, function(attack, name){
return <AttackSlot key={name} name={name} {...attack} />
})
},
renderSpells : function(){
var self = this;
return _.map(this.props.spells, function(spell, name){
return <AttackSlot key={name} name={name} {...spell} />
})
},
renderAbilities : function(){
return _.map(this.props.abilities, function(text, name){
return <div className='ability' key={name}>
<span className='name'>{name}</span>: {text}
</div>
});
},
renderItems : function(){
var self = this;
var usedItems = this.state.usedItems.slice(0);
return _.map(this.props.items, function(item, index){
var used = _.contains(usedItems, item);
if(used){
usedItems.splice(usedItems.indexOf(item), 1);
}
return <span
key={index}
className={cx({'used' : used})}
onClick={self.addUsed.bind(self, item, used)}>
{item}
</span>
});
},
render : function(){
var self = this;
var condition = ''
if(this.props.currentHP + this.state.tempHP > this.props.hp) condition='overhealed';
if(this.props.currentHP + this.state.tempHP <= this.props.hp * 0.5) condition='hurt';
if(this.props.currentHP + this.state.tempHP <= this.props.hp * 0.2) condition='last_legs';
if(this.props.currentHP + this.state.tempHP <= 0) condition='dead';
return(
<div className={cx('monsterCard', condition)}>
<div className='healthbar' style={{width : (this.props.currentHP + this.state.tempHP)/this.props.hp*100 + '%'}} />
<div className='overhealbar' style={{width : (this.props.currentHP + this.state.tempHP - this.props.hp)/this.props.hp*100 + '%'}} />
{this.renderHPBox()}
<div className='info'>
<span className='name'>{this.props.name}</span>
</div>
<div className='attackContainer'>
{this.renderAttacks()}
</div>
<div className='spellContainer'>
{this.renderSpells()}
</div>
<div className='abilitiesContainer'>
{this.renderAbilities()}
</div>
<div className='itemContainer'>
<i className='fa fa-flask' />
{this.renderItems()}
</div>
</div>
);
}
});
module.exports = MonsterCard;
/*
{this.props.initiative}
<i className='fa fa-times' onClick={this.props.remove} />
*/

View File

@@ -1,129 +0,0 @@
@marginSize : 10px;
.playerCard{
display : inline-block;
box-sizing : border-box;
margin : @marginSize;
padding : 10px;
background-color : white;
border : 1px solid #bbb;
.name{
margin-right : 20px;
}
.initiative{
font-size : 0.8em;
i{
font-size : 0.8em;
}
}
&:nth-child(5n + 1){ background-color: fade(@blue, 25%); }
&:nth-child(5n + 2){ background-color: fade(@purple, 25%); }
&:nth-child(5n + 3){ background-color: fade(@steel, 25%); }
&:nth-child(5n + 4){ background-color: fade(@green, 25%); }
&:nth-child(5n + 5){ background-color: fade(@orange, 25%); }
}
.monsterCard{
position : relative;
display : inline-block;
vertical-align : top;
box-sizing : border-box;
width : 220px;
margin : @marginSize;
padding : 10px;
background-color : white;
border : 1px solid #bbb;
.healthbar{
position : absolute;
top : 0px;
left : 0px;
z-index : 50;
height : 3px;
max-width : 100%;
background-color : @green;
}
.overhealbar{
position : absolute;
top : 0px;
left : 0px;
z-index : 100;
height : 3px;
max-width : 100%;
background-color : @blueLight;
}
&.hurt{
.healthbar{
background-color : orange;
}
}
&.last_legs{
background-color : lighten(@red, 49%);
.healthbar{
background-color : red;
}
}
&.dead{
opacity : 0.3;
}
&>.info{
margin-bottom : 10px;
.name{
margin-right : 10px;
font-size : 1.5em;
}
.stat{
margin-right : 5px;
font-size : 0.7em;
i{
font-size : 0.7em;
}
}
}
.hpBox{
.noselect();
position : absolute;
top : 5px;
right : 5px;
cursor : pointer;
text-align : right;
.currentHP{
font-size : 2em;
font-weight : 800;
line-height : 0.8em;
.tempHP{
vertical-align : top;
font-size : 0.4em;
line-height : 0.8em;
}
}
.stat{
font-size : 0.8em;
}
.hpText{
font-size : 0.6em;
font-weight : 800;
}
}
.abilitiesContainer{
margin-top : 10px;
.ability{
font-size: 0.7em;
.name{
font-weight: 800;
}
}
}
.itemContainer{
margin-top : 10px;
i{
font-size : 0.7em;
}
span{
margin-right : 5px;
cursor : pointer;
font-size : 0.7em;
&.used{
text-decoration : line-through;
}
}
}
}

View File

@@ -1,59 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var RollDice = require('naturalCrit/rollDice');
var DmDice = React.createClass({
getInitialState: function() {
return {
lastRoll:{ },
diceNotation : {
a : "1d20",
b : "6d6 + 3",
c : "1d20 - 1"
}
};
},
roll : function(id){
this.state.lastRoll[id] = RollDice(this.state.diceNotation[id]);
this.setState({
lastRoll : this.state.lastRoll
});
},
handleChange : function(id, e){
this.state.diceNotation[id] = e.target.value;
this.setState({
diceNotation : this.state.diceNotation
});
e.stopPropagation();
e.preventDefault();
},
renderRolls : function(){
var self = this;
return _.map(['a', 'b', 'c'], function(id){
return <div className='roll' key={id} onClick={self.roll.bind(self, id)}>
<input type="text" value={self.state.diceNotation[id]} onChange={self.handleChange.bind(self, id)} />
<i className='fa fa-random' />
<span key={self.state.lastRoll[id]}>{self.state.lastRoll[id]}</span>
</div>
})
},
render : function(){
var self = this;
return(
<div className='dmDice'>
<h3> <i className='fa fa-random' /> DM Dice </h3>
{this.renderRolls()}
</div>
);
}
});
module.exports = DmDice;

View File

@@ -1,32 +0,0 @@
.dmDice{
h3{
color : white;
background-color: @teal;
}
.roll{
cursor: pointer;
.noselect();
input[type="text"]{
margin-left: 10px;
margin-bottom: 6px;
margin-top: 6px;
width : 60px;
font-family: monospace;
padding : 5px;
}
i.fa-random{
font-size: 0.8em;
margin: 0 10px;
}
span{
font-weight: 800;
.fadeInLeft();
}
&:hover{
background-color: fade(@teal, 20%);
}
}
}

View File

@@ -1,100 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var JSONFileEditor = require('naturalCrit/jsonFileEditor/jsonFileEditor.jsx');
//var GetRandomEncounter = require('naturalCrit/randomEncounter.js');
var Store = require('naturalCrit/combat.store.js');
var Actions = require('naturalCrit/combat.actions.js');
var Encounters = React.createClass({
mixins : [Store.mixin()],
onStoreChange : function(){
this.setState({
encounters : Store.getEncounters(),
selectedEncounter : Store.getSelectedEncounterIndex()
});
},
getInitialState: function() {
return {
encounters : Store.getEncounters(),
selectedEncounter : Store.getSelectedEncounterIndex()
};
},
/*
getDefaultProps: function() {
return {
encounters : [],
selectedEncounter : 0,
onJSONChange : function(encounterIndex, json){},
onSelectEncounter : function(encounterIndex){},
onRemoveEncounter : function(encounterIndex){}
};
},
*/
handleJSONChange : function(encounterIndex, json){
//this.props.onJSONChange(encounterIndex, json);
Actions.updateEncounter(encounterIndex, json);
},
handleSelectEncounter : function(encounterIndex){
//this.props.onSelectEncounter(encounterIndex);
Actions.selectEncounter(encounterIndex);
},
handleRemoveEncounter : function(encounterIndex){
//this.props.onRemoveEncounter(encounterIndex);
Actions.removeEncounter(encounterIndex);
},
addRandomEncounter : function(){
Actions.addEncounter();
},
renderEncounters : function(){
var self = this;
return _.map(this.state.encounters, function(encounter, index){
var isSelected = self.state.selectedEncounter == index;
return <div className={cx('encounter' , {'selected' : isSelected})} key={index}>
<i onClick={self.handleSelectEncounter.bind(self, index)} className={cx('select', 'fa', {
'fa-square-o' : !isSelected,
'fa-check-square-o' : isSelected,
})} />
<JSONFileEditor
name={encounter.name}
json={encounter}
onJSONChange={self.handleJSONChange.bind(self, index)}
/>
<i onClick={self.handleRemoveEncounter.bind(self, index)} className='remove fa fa-times' />
</div>
})
},
render : function(){
var self = this;
return(
<div className='encounters'>
<h3>
<i className='fa fa-flag' /> Encounters
<button className='addEncounter'>
<i className='fa fa-plus' onClick={this.addRandomEncounter}/>
</button>
</h3>
{this.renderEncounters()}
<div className='controls'>
</div>
</div>
);
}
});
module.exports = Encounters;

View File

@@ -1,53 +0,0 @@
.encounters{
margin-bottom : 20px;
h3{
background-color : @red;
color : white;
button{
.animate(color);
float : right;
cursor : pointer;
background-color : transparent;
border : none;
outline : none;
&:hover{
color : white;
}
}
}
.encounter{
position : relative;
padding-left : 15px;
border-left : 0px solid @teal;
.animateAll();
&:hover{
i.remove{
opacity : 1;
}
}
i.remove{
.animate(opacity);
position : absolute;
top : 3px;
right : 3px;
cursor : pointer;
opacity : 0;
font-size : 0.6em;
color : #333;
&:hover{
color : @red;
}
}
i.select{
cursor : pointer;
}
.jsonFileEditor{
display : inline-block;
}
&.selected{
//background-color : fade(@green, 30%);
border-left : 8px solid @teal;
}
}
}

View File

@@ -1,76 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var JSONFileEditor = require('naturalCrit/jsonFileEditor/jsonFileEditor.jsx');
var DMDice = require('./dmDice/dmDice.jsx');
var Encounters = require('./encounters/encounters.jsx');
var Store = require('naturalCrit/combat.store.js');
var Actions = require('naturalCrit/combat.actions.js');
var Sidebar = React.createClass({
mixins : [Store.mixin()],
getInitialState: function() {
return {
hide : false,
monsterManual : Store.getMonsterManual(),
players : Store.getPlayersText()
};
},
onStoreChange : function(){
this.setState({
players : Store.getPlayersText(),
monsterManual : Store.getMonsterManual()
})
},
handleLogoClick : function(){
this.setState({
hide : !this.state.hide
})
},
handleMonsterManualChange : function(json){
Actions.updateMonsterManual(json);
},
handlePlayerChange : function(e){
Actions.updatePlayers(e.target.value);
},
render : function(){
var self = this;
return(
<div className={cx('sidebar', {'hide' : this.state.hide})}>
<div className='logo'>
<svg onClick={this.handleLogoClick} version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100"><path d="M80.644,87.982l16.592-41.483c0.054-0.128,0.088-0.26,0.108-0.394c0.006-0.039,0.007-0.077,0.011-0.116 c0.007-0.087,0.008-0.174,0.002-0.26c-0.003-0.046-0.007-0.091-0.014-0.137c-0.014-0.089-0.036-0.176-0.063-0.262 c-0.012-0.034-0.019-0.069-0.031-0.103c-0.047-0.118-0.106-0.229-0.178-0.335c-0.004-0.006-0.006-0.012-0.01-0.018L67.999,3.358 c-0.01-0.013-0.003-0.026-0.013-0.04L68,3.315V4c0,0-0.033,0-0.037,0c-0.403-1-1.094-1.124-1.752-0.976 c0,0.004-0.004-0.012-0.007-0.012C66.201,3.016,66.194,3,66.194,3H66.19h-0.003h-0.003h-0.004h-0.003c0,0-0.004,0-0.007,0 s-0.003-0.151-0.007-0.151L20.495,15.227c-0.025,0.007-0.046-0.019-0.071-0.011c-0.087,0.028-0.172,0.041-0.253,0.083 c-0.054,0.027-0.102,0.053-0.152,0.085c-0.051,0.033-0.101,0.061-0.147,0.099c-0.044,0.036-0.084,0.073-0.124,0.113 c-0.048,0.048-0.093,0.098-0.136,0.152c-0.03,0.039-0.059,0.076-0.085,0.117c-0.046,0.07-0.084,0.145-0.12,0.223 c-0.011,0.023-0.027,0.042-0.036,0.066L2.911,57.664C2.891,57.715,3,57.768,3,57.82v0.002c0,0.186,0,0.375,0,0.562 c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004v0.002c0,0.074-0.002,0.15,0.012,0.223 C3.015,58.631,3,58.631,3,58.633c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004c0,0,0,0,0,0.002v0.004 c0,0.191-0.046,0.377,0.06,0.545c0-0.002-0.03,0.004-0.03,0.004c0,0.004-0.03,0.004-0.03,0.004c0,0.002,0,0.002,0,0.002 l-0.045,0.004c0.03,0.047,0.036,0.09,0.068,0.133l29.049,37.359c0.002,0.004,0,0.006,0.002,0.01c0.002,0.002,0,0.004,0.002,0.008 c0.006,0.008,0.014,0.014,0.021,0.021c0.024,0.029,0.052,0.051,0.078,0.078c0.027,0.029,0.053,0.057,0.082,0.082 c0.03,0.027,0.055,0.062,0.086,0.088c0.026,0.02,0.057,0.033,0.084,0.053c0.04,0.027,0.081,0.053,0.123,0.076 c0.005,0.004,0.01,0.008,0.016,0.01c0.087,0.051,0.176,0.09,0.269,0.123c0.042,0.014,0.082,0.031,0.125,0.043 c0.021,0.006,0.041,0.018,0.062,0.021c0.123,0.027,0.249,0.043,0.375,0.043c0.099,0,0.202-0.012,0.304-0.027l45.669-8.303 c0.057-0.01,0.108-0.021,0.163-0.037C79.547,88.992,79.562,89,79.575,89c0.004,0,0.004,0,0.004,0c0.021,0,0.039-0.027,0.06-0.035 c0.041-0.014,0.08-0.034,0.12-0.052c0.021-0.01,0.044-0.019,0.064-0.03c0.017-0.01,0.026-0.015,0.033-0.017 c0.014-0.008,0.023-0.021,0.037-0.028c0.14-0.078,0.269-0.174,0.38-0.285c0.014-0.016,0.024-0.034,0.038-0.048 c0.109-0.119,0.201-0.252,0.271-0.398c0.006-0.01,0.016-0.018,0.021-0.029c0.004-0.008,0.008-0.017,0.011-0.026 c0.002-0.004,0.003-0.006,0.005-0.01C80.627,88.021,80.635,88.002,80.644,87.982z M77.611,84.461L48.805,66.453l32.407-25.202 L77.611,84.461z M46.817,63.709L35.863,23.542l43.818,14.608L46.817,63.709z M84.668,40.542l8.926,5.952l-11.902,29.75 L84.668,40.542z M89.128,39.446L84.53,36.38l-6.129-12.257L89.128,39.446z M79.876,34.645L37.807,20.622L65.854,6.599L79.876,34.645 z M33.268,19.107l-6.485-2.162l23.781-6.487L33.268,19.107z M21.92,18.895l8.67,2.891L10.357,47.798L21.92,18.895z M32.652,24.649 l10.845,39.757L7.351,57.178L32.652,24.649z M43.472,67.857L32.969,92.363L8.462,60.855L43.472,67.857z M46.631,69.09l27.826,17.393 l-38.263,6.959L46.631,69.09z"></path></svg>
<span className='name'>
<div>Natural<span className='crit'>Crit</span></div>
<small>Combat Manager</small>
</span>
</div>
<div className='contents'>
<div className='monsterManualContainer'>
<JSONFileEditor
name="Monster Manual"
json={this.state.monsterManual}
onJSONChange={this.handleMonsterManualChange}
/>
</div>
<Encounters />
<div className='addPlayers'>
<h3> <i className='fa fa-group' /> Players </h3>
<textarea value={this.state.players} onChange={this.handlePlayerChange} />
</div>
<DMDice />
</div>
</div>
);
}
});
module.exports = Sidebar;

View File

@@ -1,90 +0,0 @@
@font-face {
font-family : CodeLight;
src : url('/assets/naturalCrit/sidebar/CODE Light.otf');
}
@font-face {
font-family : CodeBold;
src : url('/assets/naturalCrit/sidebar/CODE Bold.otf');
}
.sidebar{
.animateAll();
float : left;
box-sizing : border-box;
height : 100%;
width : @sidebarWidth;
padding-bottom : 20px;
background-color : white;
//border : 1px solid @steel;
&.hide{
height : 50px;
width : 50px;
.logo .name{
left : -200px;
opacity : 0;
}
.contents{
height : 0px;
opacity : 0;
}
}
.logo{
padding : 10px 10px;
background-color : @steel;
font-family : 'CodeLight', sans-serif;
font-size : 1.8em;
color : white;
svg{
vertical-align : middle;
height : 1em;
margin-right : 0.2em;
cursor : pointer;
fill : white;
}
span.name{
.animateAll();
position : absolute;
top : 15px;
left : 50px;
opacity : 1;
font-size: 0.9em;
line-height: 0.5em;
span.crit{
font-family : 'CodeBold';
}
small{
font-size: 0.3em;
font-family : 'Open Sans';
font-weight: 800;
text-transform: uppercase;
}
}
}
.contents{
.animate(opacity);
box-sizing : border-box;
width : 100%;
&>*{
width : 100%;
}
h3{
padding : 10px;
font-size : 0.8em;
font-weight : 800;
text-transform : uppercase;
}
.addPlayers{
h3{
color : white;
background-color: @purple;
}
textarea{
height : 80px;
width : 100px;
margin : 10px;
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,52 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Router = require('pico-router');
var Icon = require('naturalCrit/icon.svg.jsx');
var Logo = require('naturalCrit/logo/logo.jsx');
var HomebrewIcon = require('naturalCrit/homebrewIcon.svg.jsx');
var CombatIcon = require('naturalCrit/combatIcon.svg.jsx');
var Home = React.createClass({
navigate : function(){
},
render : function(){
var self = this;
return(
<div className='home'>
<div className='top'>
<Logo />
<p>Top-tier tools for the discerning DM</p>
</div>
<div className='tools'>
<div className='homebrew toolContainer' onClick={Router.navigate.bind(self, '/homebrew')}>
<div className='content'>
<HomebrewIcon />
<h2>The Homebrewery</h2>
<p>Make authentic-looking 5e homebrews using Markdown</p>
</div>
</div>
<div className='combat toolContainer underConstruction' onClick={Router.navigate.bind(self, '/combat')}>
<div className='content'>
<CombatIcon />
<h2>Combat Manager</h2>
<p>Easily create and manage complex encouters for your party</p>
</div>
</div>
</div>
</div>
);
}
});
module.exports = Home;

View File

@@ -1,37 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var CreateRouter = require('pico-router').createRouter;
var Home = require('./home/home.jsx');
//var CombatManager = require('./combatManager/combatManager.jsx');
//var Homebrew = require('./homebrew/homebrew.jsx');
var Router = CreateRouter({
'/' : <Home />,
//'/combat' : <CombatManager />,
//'/homebrew' : <Homebrew />,
});
var NaturalCrit = React.createClass({
getDefaultProps: function() {
return {
url : '/',
changelog : ''
};
},
render : function(){
return <div className='naturalCrit'>
<Router initialUrl={this.props.url} scope={this}/>
</div>
},
});
module.exports = NaturalCrit;

View File

@@ -1,39 +0,0 @@
@import 'naturalCrit/styles/reset.less';
//@import 'naturalCrit/styles/elements.less';
@import 'naturalCrit/styles/animations.less';
@import 'naturalCrit/styles/colors.less';
@import 'naturalCrit/styles/tooltip.less';
@import 'font-awesome/css/font-awesome.css';
html,body, #reactContainer, .naturalCrit{
min-height : 100%;
}
@sidebarWidth : 250px;
body{
background-color : #eee;
font-family : 'Open Sans', sans-serif;
color : #4b5055;
font-weight : 100;
text-rendering : optimizeLegibility;
margin : 0;
padding : 0;
height : 100%;
}
.naturalCrit{
color : #333;
background-color: #eee;
}
.noselect(){
-webkit-touch-callout : none;
-webkit-user-select : none;
-khtml-user-select : none;
-moz-user-select : none;
-ms-user-select : none;
user-select : none;
}

View File

@@ -2,24 +2,12 @@
<html>
<head>
<script>global=window</script>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet" />
<link href="//netdna.bootstrapcdn.com/font-awesome/4.6.2/css/font-awesome.min.css" rel="stylesheet" />
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
<link rel="icon" href="/assets/naturalCrit/favicon.ico" type="image/x-icon" />
<link rel="icon" href="/assets/main/favicon.ico" type="image/x-icon" />
{{=vitreum.css}}
{{=vitreum.globals}}
<title>Natural Crit - D&D Tools</title>
{{? vitreum.inProduction}}
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-72212009-1', 'auto');
ga('send', 'pageview');
</script>
{{?}}
</head>
<body>
<div id="reactContainer">{{=vitreum.component}}</div>
@@ -27,4 +15,16 @@
{{=vitreum.libs}}
{{=vitreum.js}}
{{=vitreum.reactRender}}
{{? vitreum.inProduction}}
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','http://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-72212009-1', 'auto');
ga('send', 'pageview');
</script>
{{?}}
</html>

View File

@@ -5,19 +5,18 @@ var gulp = require("gulp");
var gulp = vitreumTasks(gulp, {
entryPoints: ["./client/naturalCrit", "./client/homebrew", "./client/admin"],
entryPoints: [
'./client/main',
'./client/homebrew',
'./client/admin'
],
DEV: true,
buildPath: "./build/",
pageTemplate: "./client/template.dot",
projectModules: ["./shared/naturalCrit"],
additionalRequirePaths : ['./shared'],
projectModules: ["./shared/naturalcrit","./shared/codemirror"],
additionalRequirePaths : ['./shared', './node_modules'],
assetExts: ["*.svg", "*.png", "*.jpg", "*.pdf", "*.eot", "*.otf", "*.woff", "*.woff2", "*.ico", "*.ttf"],
serverWatchPaths: ["server"],
serverScript: "server.js",
libs: [
@@ -25,12 +24,14 @@ var gulp = vitreumTasks(gulp, {
"react-dom",
"lodash",
"classnames",
"jsoneditor",
//From ./shared
"codemirror",
"codemirror/mode/gfm/gfm.js",
'codemirror/mode/javascript/javascript.js',
"moment",
"superagent",
"marked",
"pico-router",
"pico-flux"

View File

@@ -1,12 +1,12 @@
{
"name": "naturalCrit",
"description": "A super rad project!",
"version": "1.4.1",
"name": "naturalcrit",
"description": "D&D Tools for the discerning DM",
"version": "2.0.0",
"scripts": {
"postinstall": "gulp prod",
"start": "node server.js"
},
"author": "",
"author": "stolksdorf",
"license": "BSD-2-Clause",
"dependencies": {
"app-module-path": "^1.0.4",
@@ -15,17 +15,17 @@
"classnames": "^2.2.0",
"express": "^4.13.3",
"gulp": "^3.9.0",
"jsoneditor": "^4.2.1",
"lodash": "^3.10.1",
"lodash": "^4.11.2",
"marked": "^0.3.5",
"moment": "^2.11.0",
"mongoose": "^4.3.3",
"pico-flux": "^1.1.0",
"pico-router": "^1.1.0",
"react": "^0.14.2",
"react-dom": "^0.14.2",
"react": "^15.0.2",
"react-dom": "^15.0.2",
"shortid": "^2.2.4",
"striptags": "^2.1.1",
"superagent": "^1.6.1",
"vitreum": "^3.1.1"
"vitreum": "^3.2.1"
}
}

View File

@@ -318,8 +318,10 @@ table {
margin-bottom: 1em;
padding: 5px 10px;
background-color: #e0e5c1;
border-top: 2px black solid;
border-bottom: 2px black solid;
border-style: solid;
border-width: 11px;
border-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAA8CAMAAADG+c2+AAAANlBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHL6OuAAAAEXRSTlMABAwYJDRIXHSLo7fL2+fz+6go4VUAAAB6SURBVHgB7dg5DsJQAANRZ9+Xuf9lQUKKEHT5LlL4HWB6W5d61m1zrT/dhm5j6/RjPCkJco761ixQFoSl0aXfKQ+y9/qoJnAEYar01q64gqytNBz4ghyDMPMHnyYiIiIiIgIzYeYfj/Z56x3g7ovAf2L4bxb/EWS/ql7LZCDx/Ry3RwAAAABJRU5ErkJggg==) 11;
border-image-outset: 9px 0px;
box-shadow: 1px 4px 14px #888;
}
.phb blockquote em {
@@ -336,11 +338,16 @@ table {
font-size: 0.352cm;
line-height: 1.1em;
}
.phb pre + blockquote {
margin-top: 11px;
}
.phb hr + blockquote {
position: relative;
background-color: #FDF1DC;
border: none;
padding-top: 15px;
background-color: #FDF1DC;
border-style: solid;
border-width: 10px;
border-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAA8CAMAAADG+c2+AAAACVBMVEX///8AAADmmihtJc8lAAAAAXRSTlMAQObYZgAAACxJREFUeAHt2EEBACAAwkCwf2hL8NK7AAuwdCw9U5Og4FgeAAAAAK7IBxPjAl3qBs+hfAnLAAAAAElFTkSuQmCC) 10;
}
.phb hr + blockquote h2 {
margin-top: -8px;
@@ -376,6 +383,7 @@ table {
column-span: 1;
background-color: transparent;
border-image: none;
border-style: none;
-webkit-column-span: 1;
}
.phb hr + blockquote hr + table tbody tr:nth-child(odd),
@@ -391,44 +399,12 @@ table {
text-indent: 0em;
}
.phb hr + blockquote hr {
position: relative;
visibility: visible;
margin: 8px 0px;
border-color: transparent;
}
.phb hr + blockquote hr:after,
.phb hr + blockquote hr:before {
content: "";
position: absolute;
left: 0px;
height: 3px;
width: 100%;
}
.phb hr + blockquote hr:before {
top: -3px;
background: linear-gradient(to right top, #9c2b1b 40%, transparent 50%);
}
.phb hr + blockquote hr:after {
top: 0px;
background: linear-gradient(to right bottom, #9c2b1b 40%, transparent 50%);
}
.phb hr + blockquote:after,
.phb hr + blockquote:before {
content: "";
position: absolute;
height: 4px;
width: 100%;
padding: 0px 3px;
background-color: #E69A28;
border: 1px solid black;
}
.phb hr + blockquote:before {
top: 0px;
left: -3px;
}
.phb hr + blockquote:after {
bottom: 0px;
left: -3px;
height: 6px;
margin: 4px 0px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAAAeCAYAAACR82geAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABh0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4zjOaXUAAAAt5JREFUaEPtWTtoVEEUHf8iaEjMZt/ME6Jx3X1zlwgavyAICoL4aRWFIFYWAUURLZQo+GkUTGHc92Y2q2AKI0oKLbQIKRS0UPxCilgEFIKCTYIfyBrPi4NImGKJyWZ3Xw4cpth9b84c7ty59w3TaXdAS3FXEz8VkLtNp5YuZjNgoTGj/1KRGNEkXoEqkM4RmLamlbG55u/RwXhj7BRDMKw3kPyKkmKflk69ebxyYTeiAJL4CLO6AynOaI/v8Buqq8wrKwPWRU+Ef7bgOy3dm4p4i0456/wmNs9MU36wLnKySO4wzHqCyLoGsw4EyfiKUcZmmalLG9YFTSVJDMKkB0ryc1np7OxsrKo2UkoLVvHFJLl5GNWHpH5bS35UUXxDWyKxwMibPljFTjdJfMP2ewbDrgee06xWxhIoGWYbycWBVViJMQvCqM/go4D4Be25u/wkr4X8qctXNiFlwjwiqx9RdQfj8ZznbvI5X2SW9f+wTFi+JPEdfIHaysd4SKdEqnXrBKt26wQVQrMFv+IE7MlKcakjLfbkKOYUVDLYXljhzOMEHEBU3UMfeDJLfIu1cbY8GDmqtPiJyHoDaph1OJOqa5wxBgyNCUi8DY3pgDEqWbc6csaotPsLUTEAE+7rdES30t/kS7wHJlzGSbU349Xy6CVfEj9gwksk1yDKxzV6LPEBlXAXxhPaE5v9pogVeOGWQAR8AR/DhIuBJ3ZnE04M8iPWEqCJRIJ8jmhohynNimKJLsbmGMnFgVVYMTn22cHtgxGdgcePZWR8Y66+fqGRN32wip1KkhgEHwaSnw8/VClaVmOklBas4ieL5A7jlHiK4qlNEz/YvqquIXqfNkmMoIJ8j+R4C2a0+ElnfSux+Waa8oN1kQUwS+ITzOhWnjir05G9PhFDMKEX7ftV1Av7b6Tiy83jlYvxJmArhPdDrzHq8Io24/G1Ub2iHbvURzN1GuP2tkTNEvNThMHYbySiA8IawOrpAAAAAElFTkSuQmCC);
background-size: 100% 100%;
border: none;
}
.phb hr + hr + blockquote {
column-count: 2;
@@ -449,6 +425,7 @@ table {
border-collapse: separate;
background-color: white;
border: initial;
border-style: solid;
border-image-outset: 37px 17px;
border-image-repeat: round;
border-image-slice: 150 200 150 200;
@@ -535,3 +512,13 @@ table {
.phb table + p {
text-indent: 1em;
}
.phb ul ul,
.phb ol ol,
.phb ul ol,
.phb ol ul {
margin-bottom: 0px;
margin-left: 1.5em;
}
.phb.print blockquote {
box-shadow: none;
}

View File

@@ -49,9 +49,9 @@ app = require('./server/homebrew.server.js')(app);
app.get('*', function (req, res) {
vitreumRender({
page: './build/naturalCrit/bundle.dot',
page: './build/main/bundle.dot',
globals:{},
prerenderWith : './client/naturalCrit/naturalCrit.jsx',
prerenderWith : './client/main/main.jsx',
initialProps: {
url: req.originalUrl
},

View File

@@ -34,13 +34,22 @@ module.exports = function(app){
getTopBrews(function(topBrews){
return res.json(topBrews);
});
})
});
app.put('/homebrew/update/:id', function(req, res){
app.post('/homebrew/api', function(req, res){
var newHomebrew = new HomebrewModel(req.body);
newHomebrew.save(function(err, obj){
if(err) return;
return res.json(obj);
})
});
app.put('/homebrew/api/update/:id', function(req, res){
HomebrewModel.find({editId : req.params.id}, function(err, objs){
if(!objs.length || err) return res.status(404).send("Can not find homebrew with that id");
var resEntry = objs[0];
resEntry.text = req.body.text;
resEntry.title = req.body.title;
resEntry.updatedAt = new Date();
resEntry.save(function(err, obj){
if(err) return res.status(500).send("Error while saving");

View File

@@ -3,8 +3,9 @@ var shortid = require('shortid');
var _ = require('lodash');
var HomebrewSchema = mongoose.Schema({
shareId : {type : String, default: shortid.generate},
editId : {type : String, default: shortid.generate},
shareId : {type : String, default: shortid.generate, index: { unique: true }},
editId : {type : String, default: shortid.generate, index: { unique: true }},
title : {type : String, default : ""},
text : {type : String, default : ""},
createdAt : { type: Date, default: Date.now },
@@ -15,20 +16,6 @@ var HomebrewSchema = mongoose.Schema({
/*
HomebrewSchema.options.toJSON.transform = function (doc, ret, options) {
delete ret._id;
delete ret.__t;
delete ret.__v;
}
HomebrewSchema.options.toObject.transform = function (doc, ret, options) {
delete ret._id;
delete ret.__t;
delete ret.__v;
}
*/
var Homebrew = mongoose.model('Homebrew', HomebrewSchema);
module.exports = {

View File

@@ -2,16 +2,19 @@ var _ = require('lodash');
var vitreumRender = require('vitreum/render');
var HomebrewModel = require('./homebrew.model.js').model;
var changelogText = require('fs').readFileSync('./changelog.md', 'utf8');
module.exports = function(app){
/*
app.get('/homebrew/new', function(req, res){
var newHomebrew = new HomebrewModel();
newHomebrew.save(function(err, obj){
return res.redirect('/homebrew/edit/' + obj.editId);
})
})
*/
//Edit Page
@@ -78,24 +81,40 @@ module.exports = function(app){
HomebrewModel.find({shareId : req.params.id}, function(err, objs){
if(err || !objs.length) return res.status(404).send('Could not find Homebrew with that id');
var resObj = null;
var brew = null;
if(objs.length){
resObj = objs[0];
brew = objs[0];
}
var content = _.map(resObj.text.split('\\page'), function(pageText){
return '<div class="phb">' + Markdown(pageText) + '</div>';
var content = _.map(brew.text.split('\\page'), function(pageText){
return '<div class="phb print">' + Markdown(pageText) + '</div>';
}).join('\n');
var title = '<title>' + resObj.text.split('\n')[0] + '</title>';
var page = '<html><head>' + title + PHBStyle + '</head><body>' + content +'</body></html>'
var dialog = '';
if(req.query && req.query.dialog) dialog = 'onload="window.print()"';
var title = '<title>' + brew.title + '</title>';
var page = `<html><head>${title} ${PHBStyle}</head><body ${dialog}>${content}</body></html>`
return res.send(page)
});
});
//Source page
String.prototype.replaceAll = function(s,r){return this.split(s).join(r)}
app.get('/homebrew/source/:id', function(req, res){
HomebrewModel.find({shareId : req.params.id}, function(err, objs){
if(err || !objs.length) return res.status(404).send('Could not find Homebrew with that id');
var brew = null;
if(objs.length) brew = objs[0];
var text = brew.text.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
return res.send(`<code><pre>${text}</pre></code>`);
});
});
//Home and 404, etc.
var welcomeText = require('fs').readFileSync('./client/homebrew/homePage/welcome_msg.txt', 'utf8');
var welcomeText = require('fs').readFileSync('./client/homebrew/pages/homePage/welcome_msg.txt', 'utf8');
var changelogText = require('fs').readFileSync('./changelog.md', 'utf8');
app.get('/homebrew*', function (req, res) {
vitreumRender({
page: './build/homebrew/bundle.dot',

View File

@@ -0,0 +1,203 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var noOptions = {};
var nonWS = /[^\s\u00a0]/;
var Pos = CodeMirror.Pos;
function firstNonWS(str) {
var found = str.search(nonWS);
return found == -1 ? 0 : found;
}
CodeMirror.commands.toggleComment = function(cm) {
cm.toggleComment();
};
CodeMirror.defineExtension("toggleComment", function(options) {
if (!options) options = noOptions;
var cm = this;
var minLine = Infinity, ranges = this.listSelections(), mode = null;
for (var i = ranges.length - 1; i >= 0; i--) {
var from = ranges[i].from(), to = ranges[i].to();
if (from.line >= minLine) continue;
if (to.line >= minLine) to = Pos(minLine, 0);
minLine = from.line;
if (mode == null) {
if (cm.uncomment(from, to, options)) mode = "un";
else { cm.lineComment(from, to, options); mode = "line"; }
} else if (mode == "un") {
cm.uncomment(from, to, options);
} else {
cm.lineComment(from, to, options);
}
}
});
// Rough heuristic to try and detect lines that are part of multi-line string
function probablyInsideString(cm, pos, line) {
return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"`]/.test(line)
}
CodeMirror.defineExtension("lineComment", function(from, to, options) {
if (!options) options = noOptions;
var self = this, mode = self.getModeAt(from);
var firstLine = self.getLine(from.line);
if (firstLine == null || probablyInsideString(self, from, firstLine)) return;
var commentString = options.lineComment || mode.lineComment;
if (!commentString) {
if (options.blockCommentStart || mode.blockCommentStart) {
options.fullLines = true;
self.blockComment(from, to, options);
}
return;
}
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line + 1 : to.line, self.lastLine() + 1);
var pad = options.padding == null ? " " : options.padding;
var blankLines = options.commentBlankLines || from.line == to.line;
self.operation(function() {
if (options.indent) {
var baseString = null;
for (var i = from.line; i < end; ++i) {
var line = self.getLine(i);
var whitespace = line.slice(0, firstNonWS(line));
if (baseString == null || baseString.length > whitespace.length) {
baseString = whitespace;
}
}
for (var i = from.line; i < end; ++i) {
var line = self.getLine(i), cut = baseString.length;
if (!blankLines && !nonWS.test(line)) continue;
if (line.slice(0, cut) != baseString) cut = firstNonWS(line);
self.replaceRange(baseString + commentString + pad, Pos(i, 0), Pos(i, cut));
}
} else {
for (var i = from.line; i < end; ++i) {
if (blankLines || nonWS.test(self.getLine(i)))
self.replaceRange(commentString + pad, Pos(i, 0));
}
}
});
});
CodeMirror.defineExtension("blockComment", function(from, to, options) {
if (!options) options = noOptions;
var self = this, mode = self.getModeAt(from);
var startString = options.blockCommentStart || mode.blockCommentStart;
var endString = options.blockCommentEnd || mode.blockCommentEnd;
if (!startString || !endString) {
if ((options.lineComment || mode.lineComment) && options.fullLines != false)
self.lineComment(from, to, options);
return;
}
var end = Math.min(to.line, self.lastLine());
if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end;
var pad = options.padding == null ? " " : options.padding;
if (from.line > end) return;
self.operation(function() {
if (options.fullLines != false) {
var lastLineHasText = nonWS.test(self.getLine(end));
self.replaceRange(pad + endString, Pos(end));
self.replaceRange(startString + pad, Pos(from.line, 0));
var lead = options.blockCommentLead || mode.blockCommentLead;
if (lead != null) for (var i = from.line + 1; i <= end; ++i)
if (i != end || lastLineHasText)
self.replaceRange(lead + pad, Pos(i, 0));
} else {
self.replaceRange(endString, to);
self.replaceRange(startString, from);
}
});
});
CodeMirror.defineExtension("uncomment", function(from, to, options) {
if (!options) options = noOptions;
var self = this, mode = self.getModeAt(from);
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end);
// Try finding line comments
var lineString = options.lineComment || mode.lineComment, lines = [];
var pad = options.padding == null ? " " : options.padding, didSomething;
lineComment: {
if (!lineString) break lineComment;
for (var i = start; i <= end; ++i) {
var line = self.getLine(i);
var found = line.indexOf(lineString);
if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1;
if (found == -1 && (i != end || i == start) && nonWS.test(line)) break lineComment;
if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment;
lines.push(line);
}
self.operation(function() {
for (var i = start; i <= end; ++i) {
var line = lines[i - start];
var pos = line.indexOf(lineString), endPos = pos + lineString.length;
if (pos < 0) continue;
if (line.slice(endPos, endPos + pad.length) == pad) endPos += pad.length;
didSomething = true;
self.replaceRange("", Pos(i, pos), Pos(i, endPos));
}
});
if (didSomething) return true;
}
// Try block comments
var startString = options.blockCommentStart || mode.blockCommentStart;
var endString = options.blockCommentEnd || mode.blockCommentEnd;
if (!startString || !endString) return false;
var lead = options.blockCommentLead || mode.blockCommentLead;
var startLine = self.getLine(start), endLine = end == start ? startLine : self.getLine(end);
var open = startLine.indexOf(startString), close = endLine.lastIndexOf(endString);
if (close == -1 && start != end) {
endLine = self.getLine(--end);
close = endLine.lastIndexOf(endString);
}
if (open == -1 || close == -1 ||
!/comment/.test(self.getTokenTypeAt(Pos(start, open + 1))) ||
!/comment/.test(self.getTokenTypeAt(Pos(end, close + 1))))
return false;
// Avoid killing block comments completely outside the selection.
// Positions of the last startString before the start of the selection, and the first endString after it.
var lastStart = startLine.lastIndexOf(startString, from.ch);
var firstEnd = lastStart == -1 ? -1 : startLine.slice(0, from.ch).indexOf(endString, lastStart + startString.length);
if (lastStart != -1 && firstEnd != -1 && firstEnd + endString.length != from.ch) return false;
// Positions of the first endString after the end of the selection, and the last startString before it.
firstEnd = endLine.indexOf(endString, to.ch);
var almostLastStart = endLine.slice(to.ch).lastIndexOf(startString, firstEnd - to.ch);
lastStart = (firstEnd == -1 || almostLastStart == -1) ? -1 : to.ch + almostLastStart;
if (firstEnd != -1 && lastStart != -1 && lastStart != to.ch) return false;
self.operation(function() {
self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)),
Pos(end, close + endString.length));
var openEnd = open + startString.length;
if (pad && startLine.slice(openEnd, openEnd + pad.length) == pad) openEnd += pad.length;
self.replaceRange("", Pos(start, open), Pos(start, openEnd));
if (lead) for (var i = start + 1; i <= end; ++i) {
var line = self.getLine(i), found = line.indexOf(lead);
if (found == -1 || nonWS.test(line.slice(0, found))) continue;
var foundEnd = found + lead.length;
if (pad && line.slice(foundEnd, foundEnd + pad.length) == pad) foundEnd += pad.length;
self.replaceRange("", Pos(i, found), Pos(i, foundEnd));
}
});
return true;
});
});

View File

@@ -0,0 +1,85 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
var modes = ["clike", "css", "javascript"];
for (var i = 0; i < modes.length; ++i)
CodeMirror.extendMode(modes[i], {blockCommentContinue: " * "});
function continueComment(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), mode, inserts = [];
for (var i = 0; i < ranges.length; i++) {
var pos = ranges[i].head, token = cm.getTokenAt(pos);
if (token.type != "comment") return CodeMirror.Pass;
var modeHere = CodeMirror.innerMode(cm.getMode(), token.state).mode;
if (!mode) mode = modeHere;
else if (mode != modeHere) return CodeMirror.Pass;
var insert = null;
if (mode.blockCommentStart && mode.blockCommentContinue) {
var end = token.string.indexOf(mode.blockCommentEnd);
var full = cm.getRange(CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line, token.end)), found;
if (end != -1 && end == token.string.length - mode.blockCommentEnd.length && pos.ch >= end) {
// Comment ended, don't continue it
} else if (token.string.indexOf(mode.blockCommentStart) == 0) {
insert = full.slice(0, token.start);
if (!/^\s*$/.test(insert)) {
insert = "";
for (var j = 0; j < token.start; ++j) insert += " ";
}
} else if ((found = full.indexOf(mode.blockCommentContinue)) != -1 &&
found + mode.blockCommentContinue.length > token.start &&
/^\s*$/.test(full.slice(0, found))) {
insert = full.slice(0, found);
}
if (insert != null) insert += mode.blockCommentContinue;
}
if (insert == null && mode.lineComment && continueLineCommentEnabled(cm)) {
var line = cm.getLine(pos.line), found = line.indexOf(mode.lineComment);
if (found > -1) {
insert = line.slice(0, found);
if (/\S/.test(insert)) insert = null;
else insert += mode.lineComment + line.slice(found + mode.lineComment.length).match(/^\s*/)[0];
}
}
if (insert == null) return CodeMirror.Pass;
inserts[i] = "\n" + insert;
}
cm.operation(function() {
for (var i = ranges.length - 1; i >= 0; i--)
cm.replaceRange(inserts[i], ranges[i].from(), ranges[i].to(), "+insert");
});
}
function continueLineCommentEnabled(cm) {
var opt = cm.getOption("continueComments");
if (opt && typeof opt == "object")
return opt.continueLineComment !== false;
return true;
}
CodeMirror.defineOption("continueComments", null, function(cm, val, prev) {
if (prev && prev != CodeMirror.Init)
cm.removeKeyMap("continueComment");
if (val) {
var key = "Enter";
if (typeof val == "string")
key = val;
else if (typeof val == "object" && val.key)
key = val.key;
var map = {name: "continueComment"};
map[key] = continueComment;
cm.addKeyMap(map);
}
});
});

View File

@@ -0,0 +1,32 @@
.CodeMirror-dialog {
position: absolute;
left: 0; right: 0;
background: inherit;
z-index: 15;
padding: .1em .8em;
overflow: hidden;
color: inherit;
}
.CodeMirror-dialog-top {
border-bottom: 1px solid #eee;
top: 0;
}
.CodeMirror-dialog-bottom {
border-top: 1px solid #eee;
bottom: 0;
}
.CodeMirror-dialog input {
border: none;
outline: none;
background: transparent;
width: 20em;
color: inherit;
font-family: monospace;
}
.CodeMirror-dialog button {
font-size: 70%;
}

157
shared/codemirror/addon/dialog/dialog.js vendored Normal file
View File

@@ -0,0 +1,157 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
// Open simple dialogs on top of an editor. Relies on dialog.css.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
function dialogDiv(cm, template, bottom) {
var wrap = cm.getWrapperElement();
var dialog;
dialog = wrap.appendChild(document.createElement("div"));
if (bottom)
dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
else
dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
if (typeof template == "string") {
dialog.innerHTML = template;
} else { // Assuming it's a detached DOM element.
dialog.appendChild(template);
}
return dialog;
}
function closeNotification(cm, newVal) {
if (cm.state.currentNotificationClose)
cm.state.currentNotificationClose();
cm.state.currentNotificationClose = newVal;
}
CodeMirror.defineExtension("openDialog", function(template, callback, options) {
if (!options) options = {};
closeNotification(this, null);
var dialog = dialogDiv(this, template, options.bottom);
var closed = false, me = this;
function close(newVal) {
if (typeof newVal == 'string') {
inp.value = newVal;
} else {
if (closed) return;
closed = true;
dialog.parentNode.removeChild(dialog);
me.focus();
if (options.onClose) options.onClose(dialog);
}
}
var inp = dialog.getElementsByTagName("input")[0], button;
if (inp) {
inp.focus();
if (options.value) {
inp.value = options.value;
if (options.selectValueOnOpen !== false) {
inp.select();
}
}
if (options.onInput)
CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
if (options.onKeyUp)
CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
CodeMirror.on(inp, "keydown", function(e) {
if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
inp.blur();
CodeMirror.e_stop(e);
close();
}
if (e.keyCode == 13) callback(inp.value, e);
});
if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
} else if (button = dialog.getElementsByTagName("button")[0]) {
CodeMirror.on(button, "click", function() {
close();
me.focus();
});
if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
button.focus();
}
return close;
});
CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
closeNotification(this, null);
var dialog = dialogDiv(this, template, options && options.bottom);
var buttons = dialog.getElementsByTagName("button");
var closed = false, me = this, blurring = 1;
function close() {
if (closed) return;
closed = true;
dialog.parentNode.removeChild(dialog);
me.focus();
}
buttons[0].focus();
for (var i = 0; i < buttons.length; ++i) {
var b = buttons[i];
(function(callback) {
CodeMirror.on(b, "click", function(e) {
CodeMirror.e_preventDefault(e);
close();
if (callback) callback(me);
});
})(callbacks[i]);
CodeMirror.on(b, "blur", function() {
--blurring;
setTimeout(function() { if (blurring <= 0) close(); }, 200);
});
CodeMirror.on(b, "focus", function() { ++blurring; });
}
});
/*
* openNotification
* Opens a notification, that can be closed with an optional timer
* (default 5000ms timer) and always closes on click.
*
* If a notification is opened while another is opened, it will close the
* currently opened one and open the new one immediately.
*/
CodeMirror.defineExtension("openNotification", function(template, options) {
closeNotification(this, close);
var dialog = dialogDiv(this, template, options && options.bottom);
var closed = false, doneTimer;
var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
function close() {
if (closed) return;
closed = true;
clearTimeout(doneTimer);
dialog.parentNode.removeChild(dialog);
}
CodeMirror.on(dialog, 'click', function(e) {
CodeMirror.e_preventDefault(e);
close();
});
if (duration)
doneTimer = setTimeout(close, duration);
return close;
});
});

View File

@@ -0,0 +1,47 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"))
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod)
else // Plain browser env
mod(CodeMirror)
})(function(CodeMirror) {
"use strict"
CodeMirror.defineOption("autoRefresh", false, function(cm, val) {
if (cm.state.autoRefresh) {
stopListening(cm, cm.state.autoRefresh)
cm.state.autoRefresh = null
}
if (val && cm.display.wrapper.offsetHeight == 0)
startListening(cm, cm.state.autoRefresh = {delay: val.delay || 250})
})
function startListening(cm, state) {
function check() {
if (cm.display.wrapper.offsetHeight) {
stopListening(cm, state)
if (cm.display.lastWrapHeight != cm.display.wrapper.clientHeight)
cm.refresh()
} else {
state.timeout = setTimeout(check, state.delay)
}
}
state.timeout = setTimeout(check, state.delay)
state.hurry = function() {
clearTimeout(state.timeout)
state.timeout = setTimeout(check, 50)
}
CodeMirror.on(window, "mouseup", state.hurry)
CodeMirror.on(window, "keyup", state.hurry)
}
function stopListening(_cm, state) {
clearTimeout(state.timeout)
CodeMirror.off(window, "mouseup", state.hurry)
CodeMirror.off(window, "keyup", state.hurry)
}
});

View File

@@ -0,0 +1,6 @@
.CodeMirror-fullscreen {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
height: auto;
z-index: 9;
}

View File

@@ -0,0 +1,41 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineOption("fullScreen", false, function(cm, val, old) {
if (old == CodeMirror.Init) old = false;
if (!old == !val) return;
if (val) setFullscreen(cm);
else setNormal(cm);
});
function setFullscreen(cm) {
var wrap = cm.getWrapperElement();
cm.state.fullScreenRestore = {scrollTop: window.pageYOffset, scrollLeft: window.pageXOffset,
width: wrap.style.width, height: wrap.style.height};
wrap.style.width = "";
wrap.style.height = "auto";
wrap.className += " CodeMirror-fullscreen";
document.documentElement.style.overflow = "hidden";
cm.refresh();
}
function setNormal(cm) {
var wrap = cm.getWrapperElement();
wrap.className = wrap.className.replace(/\s*CodeMirror-fullscreen\b/, "");
document.documentElement.style.overflow = "";
var info = cm.state.fullScreenRestore;
wrap.style.width = info.width; wrap.style.height = info.height;
window.scrollTo(info.scrollLeft, info.scrollTop);
cm.refresh();
}
});

112
shared/codemirror/addon/display/panel.js vendored Normal file
View File

@@ -0,0 +1,112 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
CodeMirror.defineExtension("addPanel", function(node, options) {
options = options || {};
if (!this.state.panels) initPanels(this);
var info = this.state.panels;
var wrapper = info.wrapper;
var cmWrapper = this.getWrapperElement();
if (options.after instanceof Panel && !options.after.cleared) {
wrapper.insertBefore(node, options.before.node.nextSibling);
} else if (options.before instanceof Panel && !options.before.cleared) {
wrapper.insertBefore(node, options.before.node);
} else if (options.replace instanceof Panel && !options.replace.cleared) {
wrapper.insertBefore(node, options.replace.node);
options.replace.clear();
} else if (options.position == "bottom") {
wrapper.appendChild(node);
} else if (options.position == "before-bottom") {
wrapper.insertBefore(node, cmWrapper.nextSibling);
} else if (options.position == "after-top") {
wrapper.insertBefore(node, cmWrapper);
} else {
wrapper.insertBefore(node, wrapper.firstChild);
}
var height = (options && options.height) || node.offsetHeight;
this._setSize(null, info.heightLeft -= height);
info.panels++;
return new Panel(this, node, options, height);
});
function Panel(cm, node, options, height) {
this.cm = cm;
this.node = node;
this.options = options;
this.height = height;
this.cleared = false;
}
Panel.prototype.clear = function() {
if (this.cleared) return;
this.cleared = true;
var info = this.cm.state.panels;
this.cm._setSize(null, info.heightLeft += this.height);
info.wrapper.removeChild(this.node);
if (--info.panels == 0) removePanels(this.cm);
};
Panel.prototype.changed = function(height) {
var newHeight = height == null ? this.node.offsetHeight : height;
var info = this.cm.state.panels;
this.cm._setSize(null, info.height += (newHeight - this.height));
this.height = newHeight;
};
function initPanels(cm) {
var wrap = cm.getWrapperElement();
var style = window.getComputedStyle ? window.getComputedStyle(wrap) : wrap.currentStyle;
var height = parseInt(style.height);
var info = cm.state.panels = {
setHeight: wrap.style.height,
heightLeft: height,
panels: 0,
wrapper: document.createElement("div")
};
wrap.parentNode.insertBefore(info.wrapper, wrap);
var hasFocus = cm.hasFocus();
info.wrapper.appendChild(wrap);
if (hasFocus) cm.focus();
cm._setSize = cm.setSize;
if (height != null) cm.setSize = function(width, newHeight) {
if (newHeight == null) return this._setSize(width, newHeight);
info.setHeight = newHeight;
if (typeof newHeight != "number") {
var px = /^(\d+\.?\d*)px$/.exec(newHeight);
if (px) {
newHeight = Number(px[1]);
} else {
info.wrapper.style.height = newHeight;
newHeight = info.wrapper.offsetHeight;
info.wrapper.style.height = "";
}
}
cm._setSize(width, info.heightLeft += (newHeight - height));
height = newHeight;
};
}
function removePanels(cm) {
var info = cm.state.panels;
cm.state.panels = null;
var wrap = cm.getWrapperElement();
info.wrapper.parentNode.replaceChild(wrap, info.wrapper);
wrap.style.height = info.setHeight;
cm.setSize = cm._setSize;
cm.setSize();
}
});

View File

@@ -0,0 +1,62 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
CodeMirror.defineOption("placeholder", "", function(cm, val, old) {
var prev = old && old != CodeMirror.Init;
if (val && !prev) {
cm.on("blur", onBlur);
cm.on("change", onChange);
cm.on("swapDoc", onChange);
onChange(cm);
} else if (!val && prev) {
cm.off("blur", onBlur);
cm.off("change", onChange);
cm.off("swapDoc", onChange);
clearPlaceholder(cm);
var wrapper = cm.getWrapperElement();
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "");
}
if (val && !cm.hasFocus()) onBlur(cm);
});
function clearPlaceholder(cm) {
if (cm.state.placeholder) {
cm.state.placeholder.parentNode.removeChild(cm.state.placeholder);
cm.state.placeholder = null;
}
}
function setPlaceholder(cm) {
clearPlaceholder(cm);
var elt = cm.state.placeholder = document.createElement("pre");
elt.style.cssText = "height: 0; overflow: visible";
elt.className = "CodeMirror-placeholder";
var placeHolder = cm.getOption("placeholder")
if (typeof placeHolder == "string") placeHolder = document.createTextNode(placeHolder)
elt.appendChild(placeHolder)
cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild);
}
function onBlur(cm) {
if (isEmpty(cm)) setPlaceholder(cm);
}
function onChange(cm) {
var wrapper = cm.getWrapperElement(), empty = isEmpty(cm);
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "") + (empty ? " CodeMirror-empty" : "");
if (empty) setPlaceholder(cm);
else clearPlaceholder(cm);
}
function isEmpty(cm) {
return (cm.lineCount() === 1) && (cm.getLine(0) === "");
}
});

View File

@@ -0,0 +1,63 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineOption("rulers", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
clearRulers(cm);
cm.off("refresh", refreshRulers);
}
if (val && val.length) {
setRulers(cm);
cm.on("refresh", refreshRulers);
}
});
function clearRulers(cm) {
for (var i = cm.display.lineSpace.childNodes.length - 1; i >= 0; i--) {
var node = cm.display.lineSpace.childNodes[i];
if (/(^|\s)CodeMirror-ruler($|\s)/.test(node.className))
node.parentNode.removeChild(node);
}
}
function setRulers(cm) {
var val = cm.getOption("rulers");
var cw = cm.defaultCharWidth();
var left = cm.charCoords(CodeMirror.Pos(cm.firstLine(), 0), "div").left;
var minH = cm.display.scroller.offsetHeight + 30;
for (var i = 0; i < val.length; i++) {
var elt = document.createElement("div");
elt.className = "CodeMirror-ruler";
var col, conf = val[i];
if (typeof conf == "number") {
col = conf;
} else {
col = conf.column;
if (conf.className) elt.className += " " + conf.className;
if (conf.color) elt.style.borderColor = conf.color;
if (conf.lineStyle) elt.style.borderLeftStyle = conf.lineStyle;
if (conf.width) elt.style.borderLeftWidth = conf.width;
}
elt.style.left = (left + col * cw) + "px";
elt.style.top = "-50px";
elt.style.bottom = "-20px";
elt.style.minHeight = minH + "px";
cm.display.lineSpace.insertBefore(elt, cm.display.cursorDiv);
}
}
function refreshRulers(cm) {
clearRulers(cm);
setRulers(cm);
}
});

View File

@@ -0,0 +1,195 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
var defaults = {
pairs: "()[]{}''\"\"",
triples: "",
explode: "[]{}"
};
var Pos = CodeMirror.Pos;
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
cm.removeKeyMap(keyMap);
cm.state.closeBrackets = null;
}
if (val) {
cm.state.closeBrackets = val;
cm.addKeyMap(keyMap);
}
});
function getOption(conf, name) {
if (name == "pairs" && typeof conf == "string") return conf;
if (typeof conf == "object" && conf[name] != null) return conf[name];
return defaults[name];
}
var bind = defaults.pairs + "`";
var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
for (var i = 0; i < bind.length; i++)
keyMap["'" + bind.charAt(i) + "'"] = handler(bind.charAt(i));
function handler(ch) {
return function(cm) { return handleChar(cm, ch); };
}
function getConfig(cm) {
var deflt = cm.state.closeBrackets;
if (!deflt) return null;
var mode = cm.getModeAt(cm.getCursor());
return mode.closeBrackets || deflt;
}
function handleBackspace(cm) {
var conf = getConfig(cm);
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
var pairs = getOption(conf, "pairs");
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var around = charsAround(cm, ranges[i].head);
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
}
for (var i = ranges.length - 1; i >= 0; i--) {
var cur = ranges[i].head;
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
}
}
function handleEnter(cm) {
var conf = getConfig(cm);
var explode = conf && getOption(conf, "explode");
if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var around = charsAround(cm, ranges[i].head);
if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
}
cm.operation(function() {
cm.replaceSelection("\n\n", null);
cm.execCommand("goCharLeft");
ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var line = ranges[i].head.line;
cm.indentLine(line, null, true);
cm.indentLine(line + 1, null, true);
}
});
}
function contractSelection(sel) {
var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
}
function handleChar(cm, ch) {
var conf = getConfig(cm);
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
var pairs = getOption(conf, "pairs");
var pos = pairs.indexOf(ch);
if (pos == -1) return CodeMirror.Pass;
var triples = getOption(conf, "triples");
var identical = pairs.charAt(pos + 1) == ch;
var ranges = cm.listSelections();
var opening = pos % 2 == 0;
var type, next;
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i], cur = range.head, curType;
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
if (opening && !range.empty()) {
curType = "surround";
} else if ((identical || !opening) && next == ch) {
if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
curType = "skipThree";
else
curType = "skip";
} else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch &&
(cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != ch)) {
curType = "addFour";
} else if (identical) {
if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, ch)) curType = "both";
else return CodeMirror.Pass;
} else if (opening && (cm.getLine(cur.line).length == cur.ch ||
isClosingBracket(next, pairs) ||
/\s/.test(next))) {
curType = "both";
} else {
return CodeMirror.Pass;
}
if (!type) type = curType;
else if (type != curType) return CodeMirror.Pass;
}
var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
var right = pos % 2 ? ch : pairs.charAt(pos + 1);
cm.operation(function() {
if (type == "skip") {
cm.execCommand("goCharRight");
} else if (type == "skipThree") {
for (var i = 0; i < 3; i++)
cm.execCommand("goCharRight");
} else if (type == "surround") {
var sels = cm.getSelections();
for (var i = 0; i < sels.length; i++)
sels[i] = left + sels[i] + right;
cm.replaceSelections(sels, "around");
sels = cm.listSelections().slice();
for (var i = 0; i < sels.length; i++)
sels[i] = contractSelection(sels[i]);
cm.setSelections(sels);
} else if (type == "both") {
cm.replaceSelection(left + right, null);
cm.triggerElectric(left + right);
cm.execCommand("goCharLeft");
} else if (type == "addFour") {
cm.replaceSelection(left + left + left + left, "before");
cm.execCommand("goCharRight");
}
});
}
function isClosingBracket(ch, pairs) {
var pos = pairs.lastIndexOf(ch);
return pos > -1 && pos % 2 == 1;
}
function charsAround(cm, pos) {
var str = cm.getRange(Pos(pos.line, pos.ch - 1),
Pos(pos.line, pos.ch + 1));
return str.length == 2 ? str : null;
}
// Project the token type that will exists after the given char is
// typed, and use it to determine whether it would cause the start
// of a string token.
function enteringString(cm, pos, ch) {
var line = cm.getLine(pos.line);
var token = cm.getTokenAt(pos);
if (/\bstring2?\b/.test(token.type)) return false;
var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4);
stream.pos = stream.start = token.start;
for (;;) {
var type1 = cm.getMode().token(stream, token.state);
if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1);
stream.start = stream.pos;
}
}
});

169
shared/codemirror/addon/edit/closetag.js vendored Normal file
View File

@@ -0,0 +1,169 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
/**
* Tag-closer extension for CodeMirror.
*
* This extension adds an "autoCloseTags" option that can be set to
* either true to get the default behavior, or an object to further
* configure its behavior.
*
* These are supported options:
*
* `whenClosing` (default true)
* Whether to autoclose when the '/' of a closing tag is typed.
* `whenOpening` (default true)
* Whether to autoclose the tag when the final '>' of an opening
* tag is typed.
* `dontCloseTags` (default is empty tags for HTML, none for XML)
* An array of tag names that should not be autoclosed.
* `indentTags` (default is block tags for HTML, none for XML)
* An array of tag names that should, when opened, cause a
* blank line to be added inside the tag, and the blank line and
* closing line to be indented.
*
* See demos/closetag.html for a usage example.
*/
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../fold/xml-fold"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../fold/xml-fold"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) {
if (old != CodeMirror.Init && old)
cm.removeKeyMap("autoCloseTags");
if (!val) return;
var map = {name: "autoCloseTags"};
if (typeof val != "object" || val.whenClosing)
map["'/'"] = function(cm) { return autoCloseSlash(cm); };
if (typeof val != "object" || val.whenOpening)
map["'>'"] = function(cm) { return autoCloseGT(cm); };
cm.addKeyMap(map);
});
var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
"source", "track", "wbr"];
var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4",
"h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"];
function autoCloseGT(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), replacements = [];
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var pos = ranges[i].head, tok = cm.getTokenAt(pos);
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
if (inner.mode.name != "xml" || !state.tagName) return CodeMirror.Pass;
var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html";
var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
var tagName = state.tagName;
if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
var lowerTagName = tagName.toLowerCase();
// Don't process the '>' at the end of an end-tag or self-closing tag
if (!tagName ||
tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) ||
tok.type == "tag" && state.type == "closeTag" ||
tok.string.indexOf("/") == (tok.string.length - 1) || // match something like <someTagName />
dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||
closingTagExists(cm, tagName, pos, state, true))
return CodeMirror.Pass;
var indent = indentTags && indexOf(indentTags, lowerTagName) > -1;
replacements[i] = {indent: indent,
text: ">" + (indent ? "\n\n" : "") + "</" + tagName + ">",
newPos: indent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1)};
}
for (var i = ranges.length - 1; i >= 0; i--) {
var info = replacements[i];
cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, "+insert");
var sel = cm.listSelections().slice(0);
sel[i] = {head: info.newPos, anchor: info.newPos};
cm.setSelections(sel);
if (info.indent) {
cm.indentLine(info.newPos.line, null, true);
cm.indentLine(info.newPos.line + 1, null, true);
}
}
}
function autoCloseCurrent(cm, typingSlash) {
var ranges = cm.listSelections(), replacements = [];
var head = typingSlash ? "/" : "</";
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var pos = ranges[i].head, tok = cm.getTokenAt(pos);
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
if (typingSlash && (tok.type == "string" || tok.string.charAt(0) != "<" ||
tok.start != pos.ch - 1))
return CodeMirror.Pass;
// Kludge to get around the fact that we are not in XML mode
// when completing in JS/CSS snippet in htmlmixed mode. Does not
// work for other XML embedded languages (there is no general
// way to go from a mixed mode to its current XML state).
var replacement;
if (inner.mode.name != "xml") {
if (cm.getMode().name == "htmlmixed" && inner.mode.name == "javascript")
replacement = head + "script";
else if (cm.getMode().name == "htmlmixed" && inner.mode.name == "css")
replacement = head + "style";
else
return CodeMirror.Pass;
} else {
if (!state.context || !state.context.tagName ||
closingTagExists(cm, state.context.tagName, pos, state))
return CodeMirror.Pass;
replacement = head + state.context.tagName;
}
if (cm.getLine(pos.line).charAt(tok.end) != ">") replacement += ">";
replacements[i] = replacement;
}
cm.replaceSelections(replacements);
ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++)
if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line)
cm.indentLine(ranges[i].head.line);
}
function autoCloseSlash(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
return autoCloseCurrent(cm, true);
}
CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); };
function indexOf(collection, elt) {
if (collection.indexOf) return collection.indexOf(elt);
for (var i = 0, e = collection.length; i < e; ++i)
if (collection[i] == elt) return i;
return -1;
}
// If xml-fold is loaded, we use its functionality to try and verify
// whether a given tag is actually unclosed.
function closingTagExists(cm, tagName, pos, state, newTag) {
if (!CodeMirror.scanForClosingTag) return false;
var end = Math.min(cm.lastLine() + 1, pos.line + 500);
var nextClose = CodeMirror.scanForClosingTag(cm, pos, null, end);
if (!nextClose || nextClose.tag != tagName) return false;
var cx = state.context;
// If the immediate wrapping context contains onCx instances of
// the same tag, a closing tag only exists if there are at least
// that many closing tags of that type following.
for (var onCx = newTag ? 1 : 0; cx && cx.tagName == tagName; cx = cx.prev) ++onCx;
pos = nextClose.to;
for (var i = 1; i < onCx; i++) {
var next = CodeMirror.scanForClosingTag(cm, pos, null, end);
if (!next || next.tag != tagName) return false;
pos = next.to;
}
return true;
}
});

View File

@@ -0,0 +1,51 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var listRE = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))(\s*)/,
emptyListRE = /^(\s*)(>[> ]*|[*+-]|(\d+)[.)])(\s*)$/,
unorderedListRE = /[*+-]\s/;
CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), replacements = [];
for (var i = 0; i < ranges.length; i++) {
var pos = ranges[i].head;
var eolState = cm.getStateAfter(pos.line);
var inList = eolState.list !== false;
var inQuote = eolState.quote !== 0;
var line = cm.getLine(pos.line), match = listRE.exec(line);
if (!ranges[i].empty() || (!inList && !inQuote) || !match) {
cm.execCommand("newlineAndIndent");
return;
}
if (emptyListRE.test(line)) {
cm.replaceRange("", {
line: pos.line, ch: 0
}, {
line: pos.line, ch: pos.ch + 1
});
replacements[i] = "\n";
} else {
var indent = match[1], after = match[5];
var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0
? match[2]
: (parseInt(match[3], 10) + 1) + match[4];
replacements[i] = "\n" + indent + bullet + after;
}
}
cm.replaceSelections(replacements);
};
});

View File

@@ -0,0 +1,120 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
(document.documentMode == null || document.documentMode < 8);
var Pos = CodeMirror.Pos;
var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
function findMatchingBracket(cm, where, strict, config) {
var line = cm.getLineHandle(where.line), pos = where.ch - 1;
var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
if (!match) return null;
var dir = match.charAt(1) == ">" ? 1 : -1;
if (strict && (dir > 0) != (pos == where.ch)) return null;
var style = cm.getTokenTypeAt(Pos(where.line, pos + 1));
var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);
if (found == null) return null;
return {from: Pos(where.line, pos), to: found && found.pos,
match: found && found.ch == match.charAt(0), forward: dir > 0};
}
// bracketRegex is used to specify which type of bracket to scan
// should be a regexp, e.g. /[[\]]/
//
// Note: If "where" is on an open bracket, then this bracket is ignored.
//
// Returns false when no bracket was found, null when it reached
// maxScanLines and gave up
function scanForBracket(cm, where, dir, style, config) {
var maxScanLen = (config && config.maxScanLineLength) || 10000;
var maxScanLines = (config && config.maxScanLines) || 1000;
var stack = [];
var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/;
var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)
: Math.max(cm.firstLine() - 1, where.line - maxScanLines);
for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {
var line = cm.getLine(lineNo);
if (!line) continue;
var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1;
if (line.length > maxScanLen) continue;
if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0);
for (; pos != end; pos += dir) {
var ch = line.charAt(pos);
if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) {
var match = matching[ch];
if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};
else stack.pop();
}
}
}
return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null;
}
function matchBrackets(cm, autoclear, config) {
// Disable brace matching in long lines, since it'll cause hugely slow updates
var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
var marks = [], ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config);
if (match && cm.getLine(match.from.line).length <= maxHighlightLen) {
var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));
if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen)
marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));
}
}
if (marks.length) {
// Kludge to work around the IE bug from issue #1193, where text
// input stops going to the textare whever this fires.
if (ie_lt8 && cm.state.focused) cm.focus();
var clear = function() {
cm.operation(function() {
for (var i = 0; i < marks.length; i++) marks[i].clear();
});
};
if (autoclear) setTimeout(clear, 800);
else return clear;
}
}
var currentlyHighlighted = null;
function doMatchBrackets(cm) {
cm.operation(function() {
if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
});
}
CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
if (old && old != CodeMirror.Init)
cm.off("cursorActivity", doMatchBrackets);
if (val) {
cm.state.matchBrackets = typeof val == "object" ? val : {};
cm.on("cursorActivity", doMatchBrackets);
}
});
CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
CodeMirror.defineExtension("findMatchingBracket", function(pos, strict, config){
return findMatchingBracket(this, pos, strict, config);
});
CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){
return scanForBracket(this, pos, dir, style, config);
});
});

View File

@@ -0,0 +1,66 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../fold/xml-fold"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../fold/xml-fold"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineOption("matchTags", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
cm.off("cursorActivity", doMatchTags);
cm.off("viewportChange", maybeUpdateMatch);
clear(cm);
}
if (val) {
cm.state.matchBothTags = typeof val == "object" && val.bothTags;
cm.on("cursorActivity", doMatchTags);
cm.on("viewportChange", maybeUpdateMatch);
doMatchTags(cm);
}
});
function clear(cm) {
if (cm.state.tagHit) cm.state.tagHit.clear();
if (cm.state.tagOther) cm.state.tagOther.clear();
cm.state.tagHit = cm.state.tagOther = null;
}
function doMatchTags(cm) {
cm.state.failedTagMatch = false;
cm.operation(function() {
clear(cm);
if (cm.somethingSelected()) return;
var cur = cm.getCursor(), range = cm.getViewport();
range.from = Math.min(range.from, cur.line); range.to = Math.max(cur.line + 1, range.to);
var match = CodeMirror.findMatchingTag(cm, cur, range);
if (!match) return;
if (cm.state.matchBothTags) {
var hit = match.at == "open" ? match.open : match.close;
if (hit) cm.state.tagHit = cm.markText(hit.from, hit.to, {className: "CodeMirror-matchingtag"});
}
var other = match.at == "close" ? match.open : match.close;
if (other)
cm.state.tagOther = cm.markText(other.from, other.to, {className: "CodeMirror-matchingtag"});
else
cm.state.failedTagMatch = true;
});
}
function maybeUpdateMatch(cm) {
if (cm.state.failedTagMatch) doMatchTags(cm);
}
CodeMirror.commands.toMatchingTag = function(cm) {
var found = CodeMirror.findMatchingTag(cm, cm.getCursor());
if (found) {
var other = found.at == "close" ? found.open : found.close;
if (other) cm.extendSelection(other.to, other.from);
}
};
});

View File

@@ -0,0 +1,27 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
CodeMirror.defineOption("showTrailingSpace", false, function(cm, val, prev) {
if (prev == CodeMirror.Init) prev = false;
if (prev && !val)
cm.removeOverlay("trailingspace");
else if (!prev && val)
cm.addOverlay({
token: function(stream) {
for (var l = stream.string.length, i = l; i && /\s/.test(stream.string.charAt(i - 1)); --i) {}
if (i > stream.pos) { stream.pos = i; return null; }
stream.pos = l;
return "trailingspace";
},
name: "trailingspace"
});
});
});

View File

@@ -0,0 +1,105 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.registerHelper("fold", "brace", function(cm, start) {
var line = start.line, lineText = cm.getLine(line);
var startCh, tokenType;
function findOpening(openCh) {
for (var at = start.ch, pass = 0;;) {
var found = at <= 0 ? -1 : lineText.lastIndexOf(openCh, at - 1);
if (found == -1) {
if (pass == 1) break;
pass = 1;
at = lineText.length;
continue;
}
if (pass == 1 && found < start.ch) break;
tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));
if (!/^(comment|string)/.test(tokenType)) return found + 1;
at = found - 1;
}
}
var startToken = "{", endToken = "}", startCh = findOpening("{");
if (startCh == null) {
startToken = "[", endToken = "]";
startCh = findOpening("[");
}
if (startCh == null) return;
var count = 1, lastLine = cm.lastLine(), end, endCh;
outer: for (var i = line; i <= lastLine; ++i) {
var text = cm.getLine(i), pos = i == line ? startCh : 0;
for (;;) {
var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
if (nextOpen < 0) nextOpen = text.length;
if (nextClose < 0) nextClose = text.length;
pos = Math.min(nextOpen, nextClose);
if (pos == text.length) break;
if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == tokenType) {
if (pos == nextOpen) ++count;
else if (!--count) { end = i; endCh = pos; break outer; }
}
++pos;
}
}
if (end == null || line == end && endCh == startCh) return;
return {from: CodeMirror.Pos(line, startCh),
to: CodeMirror.Pos(end, endCh)};
});
CodeMirror.registerHelper("fold", "import", function(cm, start) {
function hasImport(line) {
if (line < cm.firstLine() || line > cm.lastLine()) return null;
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
if (start.type != "keyword" || start.string != "import") return null;
// Now find closing semicolon, return its position
for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) {
var text = cm.getLine(i), semi = text.indexOf(";");
if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)};
}
}
var start = start.line, has = hasImport(start), prev;
if (!has || hasImport(start - 1) || ((prev = hasImport(start - 2)) && prev.end.line == start - 1))
return null;
for (var end = has.end;;) {
var next = hasImport(end.line + 1);
if (next == null) break;
end = next.end;
}
return {from: cm.clipPos(CodeMirror.Pos(start, has.startCh + 1)), to: end};
});
CodeMirror.registerHelper("fold", "include", function(cm, start) {
function hasInclude(line) {
if (line < cm.firstLine() || line > cm.lastLine()) return null;
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
}
var start = start.line, has = hasInclude(start);
if (has == null || hasInclude(start - 1) != null) return null;
for (var end = start;;) {
var next = hasInclude(end + 1);
if (next == null) break;
++end;
}
return {from: CodeMirror.Pos(start, has + 1),
to: cm.clipPos(CodeMirror.Pos(end))};
});
});

View File

@@ -0,0 +1,59 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.registerGlobalHelper("fold", "comment", function(mode) {
return mode.blockCommentStart && mode.blockCommentEnd;
}, function(cm, start) {
var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd;
if (!startToken || !endToken) return;
var line = start.line, lineText = cm.getLine(line);
var startCh;
for (var at = start.ch, pass = 0;;) {
var found = at <= 0 ? -1 : lineText.lastIndexOf(startToken, at - 1);
if (found == -1) {
if (pass == 1) return;
pass = 1;
at = lineText.length;
continue;
}
if (pass == 1 && found < start.ch) return;
if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) &&
(lineText.slice(found - endToken.length, found) == endToken ||
!/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) {
startCh = found + startToken.length;
break;
}
at = found - 1;
}
var depth = 1, lastLine = cm.lastLine(), end, endCh;
outer: for (var i = line; i <= lastLine; ++i) {
var text = cm.getLine(i), pos = i == line ? startCh : 0;
for (;;) {
var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
if (nextOpen < 0) nextOpen = text.length;
if (nextClose < 0) nextClose = text.length;
pos = Math.min(nextOpen, nextClose);
if (pos == text.length) break;
if (pos == nextOpen) ++depth;
else if (!--depth) { end = i; endCh = pos; break outer; }
++pos;
}
}
if (end == null || line == end && endCh == startCh) return;
return {from: CodeMirror.Pos(line, startCh),
to: CodeMirror.Pos(end, endCh)};
});
});

149
shared/codemirror/addon/fold/foldcode.js vendored Normal file
View File

@@ -0,0 +1,149 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
function doFold(cm, pos, options, force) {
if (options && options.call) {
var finder = options;
options = null;
} else {
var finder = getOption(cm, options, "rangeFinder");
}
if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
var minSize = getOption(cm, options, "minFoldSize");
function getRange(allowFolded) {
var range = finder(cm, pos);
if (!range || range.to.line - range.from.line < minSize) return null;
var marks = cm.findMarksAt(range.from);
for (var i = 0; i < marks.length; ++i) {
if (marks[i].__isFold && force !== "fold") {
if (!allowFolded) return null;
range.cleared = true;
marks[i].clear();
}
}
return range;
}
var range = getRange(true);
if (getOption(cm, options, "scanUp")) while (!range && pos.line > cm.firstLine()) {
pos = CodeMirror.Pos(pos.line - 1, 0);
range = getRange(false);
}
if (!range || range.cleared || force === "unfold") return;
var myWidget = makeWidget(cm, options);
CodeMirror.on(myWidget, "mousedown", function(e) {
myRange.clear();
CodeMirror.e_preventDefault(e);
});
var myRange = cm.markText(range.from, range.to, {
replacedWith: myWidget,
clearOnEnter: true,
__isFold: true
});
myRange.on("clear", function(from, to) {
CodeMirror.signal(cm, "unfold", cm, from, to);
});
CodeMirror.signal(cm, "fold", cm, range.from, range.to);
}
function makeWidget(cm, options) {
var widget = getOption(cm, options, "widget");
if (typeof widget == "string") {
var text = document.createTextNode(widget);
widget = document.createElement("span");
widget.appendChild(text);
widget.className = "CodeMirror-foldmarker";
}
return widget;
}
// Clumsy backwards-compatible interface
CodeMirror.newFoldFunction = function(rangeFinder, widget) {
return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };
};
// New-style interface
CodeMirror.defineExtension("foldCode", function(pos, options, force) {
doFold(this, pos, options, force);
});
CodeMirror.defineExtension("isFolded", function(pos) {
var marks = this.findMarksAt(pos);
for (var i = 0; i < marks.length; ++i)
if (marks[i].__isFold) return true;
});
CodeMirror.commands.toggleFold = function(cm) {
cm.foldCode(cm.getCursor());
};
CodeMirror.commands.fold = function(cm) {
cm.foldCode(cm.getCursor(), null, "fold");
};
CodeMirror.commands.unfold = function(cm) {
cm.foldCode(cm.getCursor(), null, "unfold");
};
CodeMirror.commands.foldAll = function(cm) {
cm.operation(function() {
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
cm.foldCode(CodeMirror.Pos(i, 0), null, "fold");
});
};
CodeMirror.commands.unfoldAll = function(cm) {
cm.operation(function() {
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
cm.foldCode(CodeMirror.Pos(i, 0), null, "unfold");
});
};
CodeMirror.registerHelper("fold", "combine", function() {
var funcs = Array.prototype.slice.call(arguments, 0);
return function(cm, start) {
for (var i = 0; i < funcs.length; ++i) {
var found = funcs[i](cm, start);
if (found) return found;
}
};
});
CodeMirror.registerHelper("fold", "auto", function(cm, start) {
var helpers = cm.getHelpers(start, "fold");
for (var i = 0; i < helpers.length; i++) {
var cur = helpers[i](cm, start);
if (cur) return cur;
}
});
var defaultOptions = {
rangeFinder: CodeMirror.fold.auto,
widget: "\u2194",
minFoldSize: 0,
scanUp: false
};
CodeMirror.defineOption("foldOptions", null);
function getOption(cm, options, name) {
if (options && options[name] !== undefined)
return options[name];
var editorOptions = cm.options.foldOptions;
if (editorOptions && editorOptions[name] !== undefined)
return editorOptions[name];
return defaultOptions[name];
}
CodeMirror.defineExtension("foldOption", function(options, name) {
return getOption(this, options, name);
});
});

View File

@@ -0,0 +1,20 @@
.CodeMirror-foldmarker {
color: blue;
text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
font-family: arial;
line-height: .3;
cursor: pointer;
}
.CodeMirror-foldgutter {
width: .7em;
}
.CodeMirror-foldgutter-open,
.CodeMirror-foldgutter-folded {
cursor: pointer;
}
.CodeMirror-foldgutter-open:after {
content: "\25BE";
}
.CodeMirror-foldgutter-folded:after {
content: "\25B8";
}

View File

@@ -0,0 +1,146 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("./foldcode"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "./foldcode"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineOption("foldGutter", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
cm.clearGutter(cm.state.foldGutter.options.gutter);
cm.state.foldGutter = null;
cm.off("gutterClick", onGutterClick);
cm.off("change", onChange);
cm.off("viewportChange", onViewportChange);
cm.off("fold", onFold);
cm.off("unfold", onFold);
cm.off("swapDoc", onChange);
}
if (val) {
cm.state.foldGutter = new State(parseOptions(val));
updateInViewport(cm);
cm.on("gutterClick", onGutterClick);
cm.on("change", onChange);
cm.on("viewportChange", onViewportChange);
cm.on("fold", onFold);
cm.on("unfold", onFold);
cm.on("swapDoc", onChange);
}
});
var Pos = CodeMirror.Pos;
function State(options) {
this.options = options;
this.from = this.to = 0;
}
function parseOptions(opts) {
if (opts === true) opts = {};
if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter";
if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open";
if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded";
return opts;
}
function isFolded(cm, line) {
var marks = cm.findMarksAt(Pos(line));
for (var i = 0; i < marks.length; ++i)
if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i];
}
function marker(spec) {
if (typeof spec == "string") {
var elt = document.createElement("div");
elt.className = spec + " CodeMirror-guttermarker-subtle";
return elt;
} else {
return spec.cloneNode(true);
}
}
function updateFoldInfo(cm, from, to) {
var opts = cm.state.foldGutter.options, cur = from;
var minSize = cm.foldOption(opts, "minFoldSize");
var func = cm.foldOption(opts, "rangeFinder");
cm.eachLine(from, to, function(line) {
var mark = null;
if (isFolded(cm, cur)) {
mark = marker(opts.indicatorFolded);
} else {
var pos = Pos(cur, 0);
var range = func && func(cm, pos);
if (range && range.to.line - range.from.line >= minSize)
mark = marker(opts.indicatorOpen);
}
cm.setGutterMarker(line, opts.gutter, mark);
++cur;
});
}
function updateInViewport(cm) {
var vp = cm.getViewport(), state = cm.state.foldGutter;
if (!state) return;
cm.operation(function() {
updateFoldInfo(cm, vp.from, vp.to);
});
state.from = vp.from; state.to = vp.to;
}
function onGutterClick(cm, line, gutter) {
var state = cm.state.foldGutter;
if (!state) return;
var opts = state.options;
if (gutter != opts.gutter) return;
var folded = isFolded(cm, line);
if (folded) folded.clear();
else cm.foldCode(Pos(line, 0), opts.rangeFinder);
}
function onChange(cm) {
var state = cm.state.foldGutter;
if (!state) return;
var opts = state.options;
state.from = state.to = 0;
clearTimeout(state.changeUpdate);
state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);
}
function onViewportChange(cm) {
var state = cm.state.foldGutter;
if (!state) return;
var opts = state.options;
clearTimeout(state.changeUpdate);
state.changeUpdate = setTimeout(function() {
var vp = cm.getViewport();
if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
updateInViewport(cm);
} else {
cm.operation(function() {
if (vp.from < state.from) {
updateFoldInfo(cm, vp.from, state.from);
state.from = vp.from;
}
if (vp.to > state.to) {
updateFoldInfo(cm, state.to, vp.to);
state.to = vp.to;
}
});
}
}, opts.updateViewportTimeSpan || 400);
}
function onFold(cm, from) {
var state = cm.state.foldGutter;
if (!state) return;
var line = from.line;
if (line >= state.from && line < state.to)
updateFoldInfo(cm, line, line + 1);
}
});

View File

@@ -0,0 +1,44 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.registerHelper("fold", "indent", function(cm, start) {
var tabSize = cm.getOption("tabSize"), firstLine = cm.getLine(start.line);
if (!/\S/.test(firstLine)) return;
var getIndent = function(line) {
return CodeMirror.countColumn(line, null, tabSize);
};
var myIndent = getIndent(firstLine);
var lastLineInFold = null;
// Go through lines until we find a line that definitely doesn't belong in
// the block we're folding, or to the end.
for (var i = start.line + 1, end = cm.lastLine(); i <= end; ++i) {
var curLine = cm.getLine(i);
var curIndent = getIndent(curLine);
if (curIndent > myIndent) {
// Lines with a greater indent are considered part of the block.
lastLineInFold = i;
} else if (!/\S/.test(curLine)) {
// Empty lines might be breaks within the block we're trying to fold.
} else {
// A non-empty line at an indent equal to or less than ours marks the
// start of another block.
break;
}
}
if (lastLineInFold) return {
from: CodeMirror.Pos(start.line, firstLine.length),
to: CodeMirror.Pos(lastLineInFold, cm.getLine(lastLineInFold).length)
};
});
});

View File

@@ -0,0 +1,49 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.registerHelper("fold", "markdown", function(cm, start) {
var maxDepth = 100;
function isHeader(lineNo) {
var tokentype = cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0));
return tokentype && /\bheader\b/.test(tokentype);
}
function headerLevel(lineNo, line, nextLine) {
var match = line && line.match(/^#+/);
if (match && isHeader(lineNo)) return match[0].length;
match = nextLine && nextLine.match(/^[=\-]+\s*$/);
if (match && isHeader(lineNo + 1)) return nextLine[0] == "=" ? 1 : 2;
return maxDepth;
}
var firstLine = cm.getLine(start.line), nextLine = cm.getLine(start.line + 1);
var level = headerLevel(start.line, firstLine, nextLine);
if (level === maxDepth) return undefined;
var lastLineNo = cm.lastLine();
var end = start.line, nextNextLine = cm.getLine(end + 2);
while (end < lastLineNo) {
if (headerLevel(end + 1, nextLine, nextNextLine) <= level) break;
++end;
nextLine = nextNextLine;
nextNextLine = cm.getLine(end + 2);
}
return {
from: CodeMirror.Pos(start.line, firstLine.length),
to: CodeMirror.Pos(end, cm.getLine(end).length)
};
});
});

182
shared/codemirror/addon/fold/xml-fold.js vendored Normal file
View File

@@ -0,0 +1,182 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var Pos = CodeMirror.Pos;
function cmp(a, b) { return a.line - b.line || a.ch - b.ch; }
var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
var nameChar = nameStartChar + "\-\:\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
var xmlTagStart = new RegExp("<(/?)([" + nameStartChar + "][" + nameChar + "]*)", "g");
function Iter(cm, line, ch, range) {
this.line = line; this.ch = ch;
this.cm = cm; this.text = cm.getLine(line);
this.min = range ? range.from : cm.firstLine();
this.max = range ? range.to - 1 : cm.lastLine();
}
function tagAt(iter, ch) {
var type = iter.cm.getTokenTypeAt(Pos(iter.line, ch));
return type && /\btag\b/.test(type);
}
function nextLine(iter) {
if (iter.line >= iter.max) return;
iter.ch = 0;
iter.text = iter.cm.getLine(++iter.line);
return true;
}
function prevLine(iter) {
if (iter.line <= iter.min) return;
iter.text = iter.cm.getLine(--iter.line);
iter.ch = iter.text.length;
return true;
}
function toTagEnd(iter) {
for (;;) {
var gt = iter.text.indexOf(">", iter.ch);
if (gt == -1) { if (nextLine(iter)) continue; else return; }
if (!tagAt(iter, gt + 1)) { iter.ch = gt + 1; continue; }
var lastSlash = iter.text.lastIndexOf("/", gt);
var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt));
iter.ch = gt + 1;
return selfClose ? "selfClose" : "regular";
}
}
function toTagStart(iter) {
for (;;) {
var lt = iter.ch ? iter.text.lastIndexOf("<", iter.ch - 1) : -1;
if (lt == -1) { if (prevLine(iter)) continue; else return; }
if (!tagAt(iter, lt + 1)) { iter.ch = lt; continue; }
xmlTagStart.lastIndex = lt;
iter.ch = lt;
var match = xmlTagStart.exec(iter.text);
if (match && match.index == lt) return match;
}
}
function toNextTag(iter) {
for (;;) {
xmlTagStart.lastIndex = iter.ch;
var found = xmlTagStart.exec(iter.text);
if (!found) { if (nextLine(iter)) continue; else return; }
if (!tagAt(iter, found.index + 1)) { iter.ch = found.index + 1; continue; }
iter.ch = found.index + found[0].length;
return found;
}
}
function toPrevTag(iter) {
for (;;) {
var gt = iter.ch ? iter.text.lastIndexOf(">", iter.ch - 1) : -1;
if (gt == -1) { if (prevLine(iter)) continue; else return; }
if (!tagAt(iter, gt + 1)) { iter.ch = gt; continue; }
var lastSlash = iter.text.lastIndexOf("/", gt);
var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt));
iter.ch = gt + 1;
return selfClose ? "selfClose" : "regular";
}
}
function findMatchingClose(iter, tag) {
var stack = [];
for (;;) {
var next = toNextTag(iter), end, startLine = iter.line, startCh = iter.ch - (next ? next[0].length : 0);
if (!next || !(end = toTagEnd(iter))) return;
if (end == "selfClose") continue;
if (next[1]) { // closing tag
for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == next[2]) {
stack.length = i;
break;
}
if (i < 0 && (!tag || tag == next[2])) return {
tag: next[2],
from: Pos(startLine, startCh),
to: Pos(iter.line, iter.ch)
};
} else { // opening tag
stack.push(next[2]);
}
}
}
function findMatchingOpen(iter, tag) {
var stack = [];
for (;;) {
var prev = toPrevTag(iter);
if (!prev) return;
if (prev == "selfClose") { toTagStart(iter); continue; }
var endLine = iter.line, endCh = iter.ch;
var start = toTagStart(iter);
if (!start) return;
if (start[1]) { // closing tag
stack.push(start[2]);
} else { // opening tag
for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == start[2]) {
stack.length = i;
break;
}
if (i < 0 && (!tag || tag == start[2])) return {
tag: start[2],
from: Pos(iter.line, iter.ch),
to: Pos(endLine, endCh)
};
}
}
}
CodeMirror.registerHelper("fold", "xml", function(cm, start) {
var iter = new Iter(cm, start.line, 0);
for (;;) {
var openTag = toNextTag(iter), end;
if (!openTag || iter.line != start.line || !(end = toTagEnd(iter))) return;
if (!openTag[1] && end != "selfClose") {
var start = Pos(iter.line, iter.ch);
var close = findMatchingClose(iter, openTag[2]);
return close && {from: start, to: close.from};
}
}
});
CodeMirror.findMatchingTag = function(cm, pos, range) {
var iter = new Iter(cm, pos.line, pos.ch, range);
if (iter.text.indexOf(">") == -1 && iter.text.indexOf("<") == -1) return;
var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch);
var start = end && toTagStart(iter);
if (!end || !start || cmp(iter, pos) > 0) return;
var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]};
if (end == "selfClose") return {open: here, close: null, at: "open"};
if (start[1]) { // closing tag
return {open: findMatchingOpen(iter, start[2]), close: here, at: "close"};
} else { // opening tag
iter = new Iter(cm, to.line, to.ch, range);
return {open: here, close: findMatchingClose(iter, start[2]), at: "open"};
}
};
CodeMirror.findEnclosingTag = function(cm, pos, range) {
var iter = new Iter(cm, pos.line, pos.ch, range);
for (;;) {
var open = findMatchingOpen(iter);
if (!open) break;
var forward = new Iter(cm, pos.line, pos.ch, range);
var close = findMatchingClose(forward, open.tag);
if (close) return {open: open, close: close};
}
};
// Used by addon/edit/closetag.js
CodeMirror.scanForClosingTag = function(cm, pos, name, end) {
var iter = new Iter(cm, pos.line, pos.ch, end ? {from: 0, to: end} : null);
return findMatchingClose(iter, name);
};
});

View File

@@ -0,0 +1,41 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var WORD = /[\w$]+/, RANGE = 500;
CodeMirror.registerHelper("hint", "anyword", function(editor, options) {
var word = options && options.word || WORD;
var range = options && options.range || RANGE;
var cur = editor.getCursor(), curLine = editor.getLine(cur.line);
var end = cur.ch, start = end;
while (start && word.test(curLine.charAt(start - 1))) --start;
var curWord = start != end && curLine.slice(start, end);
var list = options && options.list || [], seen = {};
var re = new RegExp(word.source, "g");
for (var dir = -1; dir <= 1; dir += 2) {
var line = cur.line, endLine = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir;
for (; line != endLine; line += dir) {
var text = editor.getLine(line), m;
while (m = re.exec(text)) {
if (line == cur.line && m[0] === curWord) continue;
if ((!curWord || m[0].lastIndexOf(curWord, 0) == 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) {
seen[m[0]] = true;
list.push(m[0]);
}
}
}
}
return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
});
});

View File

@@ -0,0 +1,60 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../../mode/css/css"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../../mode/css/css"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var pseudoClasses = {link: 1, visited: 1, active: 1, hover: 1, focus: 1,
"first-letter": 1, "first-line": 1, "first-child": 1,
before: 1, after: 1, lang: 1};
CodeMirror.registerHelper("hint", "css", function(cm) {
var cur = cm.getCursor(), token = cm.getTokenAt(cur);
var inner = CodeMirror.innerMode(cm.getMode(), token.state);
if (inner.mode.name != "css") return;
if (token.type == "keyword" && "!important".indexOf(token.string) == 0)
return {list: ["!important"], from: CodeMirror.Pos(cur.line, token.start),
to: CodeMirror.Pos(cur.line, token.end)};
var start = token.start, end = cur.ch, word = token.string.slice(0, end - start);
if (/[^\w$_-]/.test(word)) {
word = ""; start = end = cur.ch;
}
var spec = CodeMirror.resolveMode("text/css");
var result = [];
function add(keywords) {
for (var name in keywords)
if (!word || name.lastIndexOf(word, 0) == 0)
result.push(name);
}
var st = inner.state.state;
if (st == "pseudo" || token.type == "variable-3") {
add(pseudoClasses);
} else if (st == "block" || st == "maybeprop") {
add(spec.propertyKeywords);
} else if (st == "prop" || st == "parens" || st == "at" || st == "params") {
add(spec.valueKeywords);
add(spec.colorKeywords);
} else if (st == "media" || st == "media_parens") {
add(spec.mediaTypes);
add(spec.mediaFeatures);
}
if (result.length) return {
list: result,
from: CodeMirror.Pos(cur.line, start),
to: CodeMirror.Pos(cur.line, end)
};
});
});

View File

@@ -0,0 +1,348 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("./xml-hint"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "./xml-hint"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var langs = "ab aa af ak sq am ar an hy as av ae ay az bm ba eu be bn bh bi bs br bg my ca ch ce ny zh cv kw co cr hr cs da dv nl dz en eo et ee fo fj fi fr ff gl ka de el gn gu ht ha he hz hi ho hu ia id ie ga ig ik io is it iu ja jv kl kn kr ks kk km ki rw ky kv kg ko ku kj la lb lg li ln lo lt lu lv gv mk mg ms ml mt mi mr mh mn na nv nb nd ne ng nn no ii nr oc oj cu om or os pa pi fa pl ps pt qu rm rn ro ru sa sc sd se sm sg sr gd sn si sk sl so st es su sw ss sv ta te tg th ti bo tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa cy wo fy xh yi yo za zu".split(" ");
var targets = ["_blank", "_self", "_top", "_parent"];
var charsets = ["ascii", "utf-8", "utf-16", "latin1", "latin1"];
var methods = ["get", "post", "put", "delete"];
var encs = ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"];
var media = ["all", "screen", "print", "embossed", "braille", "handheld", "print", "projection", "screen", "tty", "tv", "speech",
"3d-glasses", "resolution [>][<][=] [X]", "device-aspect-ratio: X/Y", "orientation:portrait",
"orientation:landscape", "device-height: [X]", "device-width: [X]"];
var s = { attrs: {} }; // Simple tag, reused for a whole lot of tags
var data = {
a: {
attrs: {
href: null, ping: null, type: null,
media: media,
target: targets,
hreflang: langs
}
},
abbr: s,
acronym: s,
address: s,
applet: s,
area: {
attrs: {
alt: null, coords: null, href: null, target: null, ping: null,
media: media, hreflang: langs, type: null,
shape: ["default", "rect", "circle", "poly"]
}
},
article: s,
aside: s,
audio: {
attrs: {
src: null, mediagroup: null,
crossorigin: ["anonymous", "use-credentials"],
preload: ["none", "metadata", "auto"],
autoplay: ["", "autoplay"],
loop: ["", "loop"],
controls: ["", "controls"]
}
},
b: s,
base: { attrs: { href: null, target: targets } },
basefont: s,
bdi: s,
bdo: s,
big: s,
blockquote: { attrs: { cite: null } },
body: s,
br: s,
button: {
attrs: {
form: null, formaction: null, name: null, value: null,
autofocus: ["", "autofocus"],
disabled: ["", "autofocus"],
formenctype: encs,
formmethod: methods,
formnovalidate: ["", "novalidate"],
formtarget: targets,
type: ["submit", "reset", "button"]
}
},
canvas: { attrs: { width: null, height: null } },
caption: s,
center: s,
cite: s,
code: s,
col: { attrs: { span: null } },
colgroup: { attrs: { span: null } },
command: {
attrs: {
type: ["command", "checkbox", "radio"],
label: null, icon: null, radiogroup: null, command: null, title: null,
disabled: ["", "disabled"],
checked: ["", "checked"]
}
},
data: { attrs: { value: null } },
datagrid: { attrs: { disabled: ["", "disabled"], multiple: ["", "multiple"] } },
datalist: { attrs: { data: null } },
dd: s,
del: { attrs: { cite: null, datetime: null } },
details: { attrs: { open: ["", "open"] } },
dfn: s,
dir: s,
div: s,
dl: s,
dt: s,
em: s,
embed: { attrs: { src: null, type: null, width: null, height: null } },
eventsource: { attrs: { src: null } },
fieldset: { attrs: { disabled: ["", "disabled"], form: null, name: null } },
figcaption: s,
figure: s,
font: s,
footer: s,
form: {
attrs: {
action: null, name: null,
"accept-charset": charsets,
autocomplete: ["on", "off"],
enctype: encs,
method: methods,
novalidate: ["", "novalidate"],
target: targets
}
},
frame: s,
frameset: s,
h1: s, h2: s, h3: s, h4: s, h5: s, h6: s,
head: {
attrs: {},
children: ["title", "base", "link", "style", "meta", "script", "noscript", "command"]
},
header: s,
hgroup: s,
hr: s,
html: {
attrs: { manifest: null },
children: ["head", "body"]
},
i: s,
iframe: {
attrs: {
src: null, srcdoc: null, name: null, width: null, height: null,
sandbox: ["allow-top-navigation", "allow-same-origin", "allow-forms", "allow-scripts"],
seamless: ["", "seamless"]
}
},
img: {
attrs: {
alt: null, src: null, ismap: null, usemap: null, width: null, height: null,
crossorigin: ["anonymous", "use-credentials"]
}
},
input: {
attrs: {
alt: null, dirname: null, form: null, formaction: null,
height: null, list: null, max: null, maxlength: null, min: null,
name: null, pattern: null, placeholder: null, size: null, src: null,
step: null, value: null, width: null,
accept: ["audio/*", "video/*", "image/*"],
autocomplete: ["on", "off"],
autofocus: ["", "autofocus"],
checked: ["", "checked"],
disabled: ["", "disabled"],
formenctype: encs,
formmethod: methods,
formnovalidate: ["", "novalidate"],
formtarget: targets,
multiple: ["", "multiple"],
readonly: ["", "readonly"],
required: ["", "required"],
type: ["hidden", "text", "search", "tel", "url", "email", "password", "datetime", "date", "month",
"week", "time", "datetime-local", "number", "range", "color", "checkbox", "radio",
"file", "submit", "image", "reset", "button"]
}
},
ins: { attrs: { cite: null, datetime: null } },
kbd: s,
keygen: {
attrs: {
challenge: null, form: null, name: null,
autofocus: ["", "autofocus"],
disabled: ["", "disabled"],
keytype: ["RSA"]
}
},
label: { attrs: { "for": null, form: null } },
legend: s,
li: { attrs: { value: null } },
link: {
attrs: {
href: null, type: null,
hreflang: langs,
media: media,
sizes: ["all", "16x16", "16x16 32x32", "16x16 32x32 64x64"]
}
},
map: { attrs: { name: null } },
mark: s,
menu: { attrs: { label: null, type: ["list", "context", "toolbar"] } },
meta: {
attrs: {
content: null,
charset: charsets,
name: ["viewport", "application-name", "author", "description", "generator", "keywords"],
"http-equiv": ["content-language", "content-type", "default-style", "refresh"]
}
},
meter: { attrs: { value: null, min: null, low: null, high: null, max: null, optimum: null } },
nav: s,
noframes: s,
noscript: s,
object: {
attrs: {
data: null, type: null, name: null, usemap: null, form: null, width: null, height: null,
typemustmatch: ["", "typemustmatch"]
}
},
ol: { attrs: { reversed: ["", "reversed"], start: null, type: ["1", "a", "A", "i", "I"] } },
optgroup: { attrs: { disabled: ["", "disabled"], label: null } },
option: { attrs: { disabled: ["", "disabled"], label: null, selected: ["", "selected"], value: null } },
output: { attrs: { "for": null, form: null, name: null } },
p: s,
param: { attrs: { name: null, value: null } },
pre: s,
progress: { attrs: { value: null, max: null } },
q: { attrs: { cite: null } },
rp: s,
rt: s,
ruby: s,
s: s,
samp: s,
script: {
attrs: {
type: ["text/javascript"],
src: null,
async: ["", "async"],
defer: ["", "defer"],
charset: charsets
}
},
section: s,
select: {
attrs: {
form: null, name: null, size: null,
autofocus: ["", "autofocus"],
disabled: ["", "disabled"],
multiple: ["", "multiple"]
}
},
small: s,
source: { attrs: { src: null, type: null, media: null } },
span: s,
strike: s,
strong: s,
style: {
attrs: {
type: ["text/css"],
media: media,
scoped: null
}
},
sub: s,
summary: s,
sup: s,
table: s,
tbody: s,
td: { attrs: { colspan: null, rowspan: null, headers: null } },
textarea: {
attrs: {
dirname: null, form: null, maxlength: null, name: null, placeholder: null,
rows: null, cols: null,
autofocus: ["", "autofocus"],
disabled: ["", "disabled"],
readonly: ["", "readonly"],
required: ["", "required"],
wrap: ["soft", "hard"]
}
},
tfoot: s,
th: { attrs: { colspan: null, rowspan: null, headers: null, scope: ["row", "col", "rowgroup", "colgroup"] } },
thead: s,
time: { attrs: { datetime: null } },
title: s,
tr: s,
track: {
attrs: {
src: null, label: null, "default": null,
kind: ["subtitles", "captions", "descriptions", "chapters", "metadata"],
srclang: langs
}
},
tt: s,
u: s,
ul: s,
"var": s,
video: {
attrs: {
src: null, poster: null, width: null, height: null,
crossorigin: ["anonymous", "use-credentials"],
preload: ["auto", "metadata", "none"],
autoplay: ["", "autoplay"],
mediagroup: ["movie"],
muted: ["", "muted"],
controls: ["", "controls"]
}
},
wbr: s
};
var globalAttrs = {
accesskey: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
"class": null,
contenteditable: ["true", "false"],
contextmenu: null,
dir: ["ltr", "rtl", "auto"],
draggable: ["true", "false", "auto"],
dropzone: ["copy", "move", "link", "string:", "file:"],
hidden: ["hidden"],
id: null,
inert: ["inert"],
itemid: null,
itemprop: null,
itemref: null,
itemscope: ["itemscope"],
itemtype: null,
lang: ["en", "es"],
spellcheck: ["true", "false"],
style: null,
tabindex: ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
title: null,
translate: ["yes", "no"],
onclick: null,
rel: ["stylesheet", "alternate", "author", "bookmark", "help", "license", "next", "nofollow", "noreferrer", "prefetch", "prev", "search", "tag"]
};
function populate(obj) {
for (var attr in globalAttrs) if (globalAttrs.hasOwnProperty(attr))
obj.attrs[attr] = globalAttrs[attr];
}
populate(s);
for (var tag in data) if (data.hasOwnProperty(tag) && data[tag] != s)
populate(data[tag]);
CodeMirror.htmlSchema = data;
function htmlHint(cm, options) {
var local = {schemaInfo: data};
if (options) for (var opt in options) local[opt] = options[opt];
return CodeMirror.hint.xml(cm, local);
}
CodeMirror.registerHelper("hint", "html", htmlHint);
});

View File

@@ -0,0 +1,146 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
var Pos = CodeMirror.Pos;
function forEach(arr, f) {
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
}
function arrayContains(arr, item) {
if (!Array.prototype.indexOf) {
var i = arr.length;
while (i--) {
if (arr[i] === item) {
return true;
}
}
return false;
}
return arr.indexOf(item) != -1;
}
function scriptHint(editor, keywords, getToken, options) {
// Find the token at the cursor
var cur = editor.getCursor(), token = getToken(editor, cur);
if (/\b(?:string|comment)\b/.test(token.type)) return;
token.state = CodeMirror.innerMode(editor.getMode(), token.state).state;
// If it's not a 'word-style' token, ignore the token.
if (!/^[\w$_]*$/.test(token.string)) {
token = {start: cur.ch, end: cur.ch, string: "", state: token.state,
type: token.string == "." ? "property" : null};
} else if (token.end > cur.ch) {
token.end = cur.ch;
token.string = token.string.slice(0, cur.ch - token.start);
}
var tprop = token;
// If it is a property, find out what it is a property of.
while (tprop.type == "property") {
tprop = getToken(editor, Pos(cur.line, tprop.start));
if (tprop.string != ".") return;
tprop = getToken(editor, Pos(cur.line, tprop.start));
if (!context) var context = [];
context.push(tprop);
}
return {list: getCompletions(token, context, keywords, options),
from: Pos(cur.line, token.start),
to: Pos(cur.line, token.end)};
}
function javascriptHint(editor, options) {
return scriptHint(editor, javascriptKeywords,
function (e, cur) {return e.getTokenAt(cur);},
options);
};
CodeMirror.registerHelper("hint", "javascript", javascriptHint);
function getCoffeeScriptToken(editor, cur) {
// This getToken, it is for coffeescript, imitates the behavior of
// getTokenAt method in javascript.js, that is, returning "property"
// type and treat "." as indepenent token.
var token = editor.getTokenAt(cur);
if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
token.end = token.start;
token.string = '.';
token.type = "property";
}
else if (/^\.[\w$_]*$/.test(token.string)) {
token.type = "property";
token.start++;
token.string = token.string.replace(/\./, '');
}
return token;
}
function coffeescriptHint(editor, options) {
return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options);
}
CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint);
var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
"toUpperCase toLowerCase split concat match replace search").split(" ");
var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
"lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
var funcProps = "prototype apply call bind".split(" ");
var javascriptKeywords = ("break case catch continue debugger default delete do else false finally for function " +
"if in instanceof new null return switch throw true try typeof var void while with").split(" ");
var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
"if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
function getCompletions(token, context, keywords, options) {
var found = [], start = token.string, global = options && options.globalScope || window;
function maybeAdd(str) {
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
}
function gatherCompletions(obj) {
if (typeof obj == "string") forEach(stringProps, maybeAdd);
else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
else if (obj instanceof Function) forEach(funcProps, maybeAdd);
for (var name in obj) maybeAdd(name);
}
if (context && context.length) {
// If this is a property, see if it belongs to some object we can
// find in the current environment.
var obj = context.pop(), base;
if (obj.type && obj.type.indexOf("variable") === 0) {
if (options && options.additionalContext)
base = options.additionalContext[obj.string];
if (!options || options.useGlobalScope !== false)
base = base || global[obj.string];
} else if (obj.type == "string") {
base = "";
} else if (obj.type == "atom") {
base = 1;
} else if (obj.type == "function") {
if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
(typeof global.jQuery == 'function'))
base = global.jQuery();
else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function'))
base = global._();
}
while (base != null && context.length)
base = base[context.pop().string];
if (base != null) gatherCompletions(base);
} else {
// If not, just look in the global object and any local scope
// (reading into JS mode internals to get at the local and global variables)
for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
if (!options || options.useGlobalScope !== false)
gatherCompletions(global);
forEach(keywords, maybeAdd);
}
return found;
}
});

Some files were not shown because too many files have changed in this diff Show More