Got new word form React component working and rendering mostly correctly in ES2016!

This commit is contained in:
Robbie Antenesse 2016-09-21 14:40:25 -06:00
parent d914d8f133
commit 15f26883e6
14 changed files with 378 additions and 32 deletions

3
.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": [ "es2015", "react" ]
}

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
php/google/
ipa_character_picker/
node_modules/
public/

View File

@ -5,7 +5,10 @@
"description": "A tool to build simple dictionaries using JSON.",
"main": "src/app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"start": "node start-server.js",
"pack": "node ./node_modules/webpack/bin/webpack.js -d --progress --display-error-details",
"watch": "node ./node_modules/webpack/bin/webpack.js -d --progress --watch",
"build": "node ./node_modules/webpack/bin/webpack.js -p --progress"
},
"repository": {
"type": "git",
@ -18,7 +21,25 @@
},
"homepage": "https://github.com/Alamantus/Lexiconga#readme",
"dependencies": {
"babel-polyfill": "^6.13.0",
"json-query": "^2.2.0",
"marked": "^0.3.6",
"papaparse": "^4.1.2"
"papaparse": "^4.1.2",
"react": "^15.3.2",
"react-dom": "^15.3.2"
},
"devDependencies": {
"babel-core": "^6.14.0",
"babel-loader": "^6.2.5",
"babel-preset-es2015": "^6.14.0",
"babel-preset-react": "^6.11.1",
"css-loader": "^0.25.0",
"express": "^4.14.0",
"file-loader": "^0.9.0",
"html-minify-loader": "^1.1.0",
"node-sass": "^3.10.0",
"sass-loader": "^4.0.2",
"style-loader": "^0.13.1",
"webpack": "^1.13.2"
}
}

View File

@ -0,0 +1,24 @@
import React from 'react';
import {Input} from './Input';
import {TextArea} from './TextArea';
import {WordForm} from './WordForm';
export class EditWordForm extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<WordForm action='edit'>
<Input name='Word' />
<Input name='Pronunciation' helperLink={{url: "http://r12a.github.io/pickers/ipa/", label: 'IPA Characters', hover:"IPA Character Picker located at http://r12a.github.io/pickers/ipa/"}} />
<Input name='Part of Speech' />
<Input name='Definition/<wbr><b class=wbr></b>Equivalent Word(s)' />
<TextArea name='Explanation/<wbr><b class=wbr></b>Long Definition' />
</WordForm>
);
}
}

59
src/components/Input.jsx Normal file
View File

@ -0,0 +1,59 @@
import React from 'react';
export class Input extends React.Component {
constructor(props) {
super(props);
// this.defaultProps = {
// name: props.name || 'Field',
// helperLink: props.helperLink || {url: '#', label: '', hover: ''},
// doValidate: props.doValidate || true
// };
this.state = {
value: props.value || ''
};
// Bind listeners
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange(event) {
this.setValue(event);
}
// Whenever the input changes we update the value state of this component
setValue(event) {
this.setState({
isValid: !(this.props.doValidate && event.currentTarget.value === ''),
value: event.currentTarget.value
});
}
showHelperLink() {
if (this.props.helperLink) {
return (
<a className='clickable inline-button' href={this.props.helperLink.url} target='_blank' title={this.props.helperLink.hover}>
{this.props.helperLink.label}
</a>
);
}
}
render() {
return (
<label>
<span>
{this.props.name}
{this.showHelperLink()}
</span>
<input type="text" onChange={this.handleOnChange} value={this.state.value} />
</label>
);
}
}
Input.defaultProps = {
name: 'Field',
doValidate: true
};

View File

@ -0,0 +1,30 @@
import React from 'react';
import {Input} from './Input';
import {TextArea} from './TextArea';
import {WordForm} from './WordForm';
export class NewWordForm extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<WordForm action='new'>
<Input name='Word' />
<Input name='Pronunciation'
helperLink={{
url: "http://r12a.github.io/pickers/ipa/",
label: "IPA Characters",
hover: "IPA Character Picker located at http://r12a.github.io/pickers/ipa/"
}} />
<Input name='Part of Speech' />
<Input name={<div style={{display: 'inline'}}>Definition/<wbr /><b className="wbr"></b>Equivalent Word(s)</div>} />
<TextArea id='newWordForm'
name={<div style={{display: 'inline'}}>Explanation/<wbr /><b className="wbr"></b>Long Definition</div>} />
</WordForm>
);
}
}

View File

@ -0,0 +1,43 @@
import React from 'react';
import {Input} from './Input';
import {getInputSelection, setSelectionRange} from '../js/helpers';
export class TextArea extends Input {
constructor(props) {
super(props);
// Bind listeners
this.handleMaximizeClick = this.handleMaximizeClick.bind(this);
}
handleMaximizeClick(event) {
this.showFullScreenTextbox();
}
showFullScreenTextbox() {
var sourceTextboxElement = document.getElementById(this.props.id);
var targetTextboxElement = document.getElementById("fullScreenTextbox");
document.getElementById("fullScreenTextboxLabel").innerHTML = this.props.name;
var selection = getInputSelection(sourceTextboxElement);
document.getElementById("expandedTextboxId").innerHTML = this.props.id;
targetTextboxElement.value = sourceTextboxElement.value;
document.getElementById("fullScreenTextboxScreen").style.display = "block";
targetTextboxElement.focus();
setSelectionRange(targetTextboxElement, selection.start, selection.end);
}
render() {
return (
<label>
<span>
{this.props.name}
<span className="clickable inline-button" onClick={this.handleMaximizeClick}>Maximize</span>
</span>
<textarea id={this.props.id} onChange={this.handleOnChange} value={this.state.value} />
</label>
);
}
}

View File

@ -0,0 +1,69 @@
import React from 'react';
import {Input} from './Input';
import {TextArea} from './TextArea';
import {keyCodeFor} from '../js/helpers';
export class WordForm extends React.Component {
constructor(props) {
super(props);
this.state = {
errorMessage: '',
updateConflictMessage: ''
};
this.isNewWord = (this.props.action == 'new');
}
submitWordOnCtrlEnter() {
var keyCode = (event.which ? event.which : event.keyCode);
//Windows and Linux Chrome accept ctrl+enter as keyCode 10.
if (keyCode === keyCodeFor('ctrlEnter') || (keyCode == keyCodeFor('enter') && event.ctrlKey)) {
event.preventDefault();
this.handleSubmit();
}
}
handleSubmit() {
if (this.validate()) {
}
}
validate() {
return true;
}
confirmArea() {
if (this.isNewWord) {
return <button type="button" onClick={this.handleSubmit}>Add Word</button>;
}
return (
<div id="editWordButtonArea">
<button type="button" onClick={this.handleSubmit}>Edit Word</button>
<button type="button" onClick={this.clearForm}>Cancel</button>
</div>
);
}
clearForm() {
this.props.children.forEach((field) => {
field.state.value = '';
})
}
render() {
return (
<form>
{this.props.children}
<span id="errorMessage">{this.state.errorMessage}</span>
{this.confirmArea}
<div id="updateConflict">{this.state.updateConflictMessage}</div>
</form>
);
}
}

View File

@ -9,11 +9,9 @@
<meta property="og:title" content="Lexiconga Dictionary Builder" />
<meta property="og:description" content="Build lexicons for contructed languages or anything that you can think of!" />
<meta property="og:image" content="http://lexicon.ga/images/logo.svg" />
<link href="/css/styles.css" rel="stylesheet" />
</head>
<body>
<site></site>
<div id="site"></div>
<script src="dictionaryBuilder.js"></script>
</body>
</html>

21
src/index.jsx Normal file
View File

@ -0,0 +1,21 @@
import './index.html';
import './sass/styles.scss';
import React from 'react';
import ReactDOM from 'react-dom';
import {NewWordForm} from './components/NewWordForm';
class Lexiconga extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<NewWordForm />
);
}
}
ReactDOM.render(<Lexiconga />, document.getElementById('site'));

View File

@ -1,21 +1,21 @@
function ready(fn) {
if (document.readyState != 'loading'){
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
// function ready(fn) {
// if (document.readyState != 'loading'){
// fn();
// } else {
// document.addEventListener('DOMContentLoaded', fn);
// }
// }
// Set Marked.js settings
marked.setOptions({
gfm: true,
tables: true,
breaks: true,
sanitize: true
});
// marked.setOptions({
// gfm: true,
// tables: true,
// breaks: true,
// sanitize: true
// });
// Get Keycode based on key name
function keyCodeFor(keyName) {
export function keyCodeFor(keyName) {
if (keyName == "backspace") return 8;
else if (keyName == "tab") return 9;
else if (keyName == "ctrlEnter") return 10;
@ -119,7 +119,7 @@ function keyCodeFor(keyName) {
else return false;
}
function getInputSelection(el) {
export function getInputSelection(el) {
// Retrieved from http://stackoverflow.com/a/4207763
var start = 0, end = 0, normalizedValue, range,
textInputRange, len, endRange;
@ -166,7 +166,7 @@ function getInputSelection(el) {
};
}
function setSelectionRange(input, selectionStart, selectionEnd) {
export function setSelectionRange(input, selectionStart, selectionEnd) {
// Retrieved from http://stackoverflow.com/a/17858641/3508346
if (input.setSelectionRange) {
input.focus();
@ -181,7 +181,7 @@ function setSelectionRange(input, selectionStart, selectionEnd) {
}
}
function SaveScroll() {
export function SaveScroll() {
var doc = document.documentElement;
var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
var top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
@ -190,36 +190,36 @@ function SaveScroll() {
savedScroll.y = top;
}
function htmlEntities(string) {
export function htmlEntities(string) {
return String(string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;').replace(/\\/g, '&#92;').replace(/\n/g, '<br>');
}
function htmlEntitiesParse(string) {
export function htmlEntitiesParse(string) {
return String(string).replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&#92;/g, '\\').replace(/<br>/g, '\n');
}
function htmlEntitiesParseForMarkdown(string) {
export function htmlEntitiesParseForMarkdown(string) {
return String(string).replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&#92;/g, '\\').replace(/<br>/g, '\n');
}
function stripHtmlEntities(string) {
export function stripHtmlEntities(string) {
// This is for the export name.
return String(string).replace(/&amp;/g, '').replace(/&lt;/g, '').replace(/&gt;/g, '').replace(/&quot;/g, '').replace(/&apos;/g, "").replace(/&#92;/g, '').replace(/<br>/g, '');
}
function htmlEntitiesParseForSearchEntry(string) {
export function htmlEntitiesParseForSearchEntry(string) {
return String(string).replace(/"/g, '%%%%').replace(/'/g, "````");
}
function htmlEntitiesParseForSearch(string) {
export function htmlEntitiesParseForSearch(string) {
return String(string).replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '%%%%').replace(/&apos;/g, "````");
}
function regexParseForSearch(string) {
export function regexParseForSearch(string) {
return String(string).replace(/([\[\\\^\$\.\|\?\*\+\(\)\{\}\]])/g, "\\$1");
}
function dynamicSort(propertiesArray) {
export function dynamicSort(propertiesArray) {
/* Retrieved from http://stackoverflow.com/a/30446887/3508346
Usage: theArray.sort(dynamicSort(['propertyAscending', '-propertyDescending']));*/
return function (a, b) {
@ -240,7 +240,7 @@ function dynamicSort(propertiesArray) {
};
}
function download(filename, text) {
export function download(filename, text) {
/* Retrieved from http://stackoverflow.com/a/18197341/3508346
Usage: download('test.txt', 'Hello world!');*/
var element = document.createElement('a');

3
src/sass/main.scss Normal file
View File

@ -0,0 +1,3 @@
@import 'styles';
// @import 'lexiconga';
// @import 'mobile';

16
start-server.js Normal file
View File

@ -0,0 +1,16 @@
var express = require('express');
var app = express();
app.use(express.static('./public/'));
app.listen(3013, function () {
setTerminalTitle('localhost:3013');
console.log('Example app listening on port 3013!');
});
function setTerminalTitle(title)
{
process.stdout.write(
String.fromCharCode(27) + "]0;" + title + String.fromCharCode(7)
);
}

59
webpack.config.js Normal file
View File

@ -0,0 +1,59 @@
const webpack = require('webpack');
const path = require('path');
const BUILD_DIR = path.resolve(__dirname, 'public');
const APP_DIR = path.resolve(__dirname, 'src');
module.exports = {
entry: APP_DIR + '/index.jsx',
output: {
path: BUILD_DIR,
filename: 'dictionaryBuilder.js'
},
module: {
loaders: [
{
test: /\.html?$/,
exclude: /node_modules/,
loaders: [
'file?name=[name].html',
'html-minify'
]
},
{
test: /\.scss$/,
exclude: /node_modules/,
loaders: ['style', 'css', 'sass']
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['react', 'es2015']
}
}
]
},
resolve: {
extensions: ['', '.js', '.jsx'],
},
// plugins: [
// When you're ready to publish, check this article out.
// http://dev.topheman.com/make-your-react-production-minified-version-with-webpack/
// new webpack.optimize.UglifyJsPlugin({
// compress: {
// warnings: false
// },
// output: {
// comments: false
// }
// })
// ],
sassLoader: {
file: './src/sass/styles.scss',
// includePaths: ['./node_modules/bootstrap-sass/assets/'],
outFile: './public/styles.css',
outputStyle: 'compressed'
}
};