<commit_before>/* @flow */

import React, { Component, PropTypes } from 'react';
import ReactNative from 'react-native';
import EvilIcons from 'react-native-vector-icons/EvilIcons';
import Colors from '../../Colors';
import AppText from './AppText';
import AppTextInput from './AppTextInput';
import StatusbarWrapper from './StatusbarWrapper';
import AppbarSecondary from './AppbarSecondary';
import AppbarTouchable from './AppbarTouchable';
import AppbarIcon from './AppbarIcon';
import GrowingTextInput from './GrowingTextInput';
import TouchFeedback from './TouchFeedback';
import Icon from './Icon';
import AvatarContainer from '../containers/AvatarContainer';
import ImageUploadContainer from '../containers/ImageUploadContainer';
import Banner from './Banner';
import ImageUploadDiscussion from './ImageUploadDiscussion';
import KeyboardSpacer from './KeyboardSpacer';
import ImageChooser from '../../modules/ImageChooser';

const {
	AsyncStorage,
	TouchableOpacity,
	StyleSheet,
	NavigationActions,
	ScrollView,
	View
} = ReactNative;

const styles = StyleSheet.create({
	container: {
		flex: 1,
		backgroundColor: Colors.white
	},
	scene: {
		paddingHorizontal: 8,
		paddingVertical: 16
	},
	threadName: {
		fontWeight: 'bold',
		fontSize: 20,
		lineHeight: 30
	},
	threadSummary: {
		fontSize: 16,
		lineHeight: 24,
		margin: 0,
	},
	icon: {
		color: Colors.fadedBlack
	},
	disabled: {
		opacity: 0.5
	},
	userIcon: {
		margin: 12
	},
	entry: {
		paddingVertical: 4,
		paddingHorizontal: 12
	},
	uploadButtonIcon: {
		color: Colors.fadedBlack,
		marginHorizontal: 16,
		marginVertical: 14
	},
	uploadButtonIconActive: {
		color: Colors.info
	},
	footer: {
		flexDirection: 'row',
		justifyContent: 'space-between',
		borderTopColor: Colors.separator,
		borderTopWidth: 1
	},
	postButton: {
		backgroundColor: Colors.info,
		margin: 6,
		width: 100,
		borderRadius: 3
	},
	postButtonInner: {
		paddingVertical: 10,
		paddingHorizontal: 16
	},
	postButtonText: {
		color: Colors.white,
		fontWeight: 'bold',
		textAlign: 'center'
	},
	socialItem: {
		flexDirection: 'row',
		alignItems: 'center',
		margin: 12
	},
	socialIconContainer: {
		height: 28,
		width: 28,
		alignItems: 'center',
		justifyContent: 'center',
		backgroundColor: Colors.grey,
		borderRadius: 2
	},
	socialIconContainerSelected: {
		backgroundColor: Colors.facebook
	},
	socialIcon: {
		fontSize: 28,
		color: Colors.white
	},
	socialTextContainer: {
		marginHorizontal: 12,
		color: Colors.fadedBlack,
		bottom: -3
	},
	socialTextSelected: {
		color: Colors.facebook,
		fontWeight: 'bold'
	},
	socialLabel: {
		fontSize: 12,
		lineHeight: 18,
	},
	socialTip: {
		fontSize: 10,
		lineHeight: 15,
		opacity: 0.5,
		fontWeight: 'normal'
	}
});

const FACEBOOK_SHARE_CHECKED_KEY = 'start_discussion_facebook_share_checked';

type Props = {
	user: string;
	room: {
		id: string
	};
	dismiss: Function;
	startThread: Function;
	onNavigation: Function;
}

type State = {
	name: string;
	body: string;
	meta: ?{
		photo: ?{
			title: string;
			height: number;
			width: number;
		};
	};
	upload: ?{
		originalUrl: string;
		thumbnailUrl: string;
	};
	status: 'loading' | null;
	error: ?string;
	shareOnFacebook: boolean;
}

export default class StartDiscussionButton extends Component<void, Props, State> {
	static propTypes = {
		user: PropTypes.string.isRequired,
		room: PropTypes.shape({
			id: PropTypes.string.isRequired
		}).isRequired,
		dismiss: PropTypes.func.isRequired,
		startThread: PropTypes.func.isRequired,
		onNavigation: PropTypes.func.isRequired
	};

	state: State = {
		name: '',
		body: '',
		meta: null,
		upload: null,
		status: null,
		error: null,
		shareOnFacebook: false
	};

	componentDidMount() {
		this._setShareCheckbox();
	}

	_handleSharePress: Function = () => {
		global.requestAnimationFrame(async () => {
			let shareOnFacebook = !this.state.shareOnFacebook;

			if (shareOnFacebook) {
				try {
					// await this.props.requestFacebookPermissions();
				} catch (err) {
					shareOnFacebook = false;
				}
			}

			this.setState({
				shareOnFacebook
			});

			AsyncStorage.setItem(FACEBOOK_SHARE_CHECKED_KEY, JSON.stringify(shareOnFacebook));
		});
	};

	_setShareCheckbox: Function = async (): Promise<any> => {
		let shareOnFacebook;

		try {
			const isEnabled = JSON.parse(await AsyncStorage.getItem(FACEBOOK_SHARE_CHECKED_KEY));

			if (isEnabled) {
				// const granted = await this.props.isFacebookPermissionGranted();
				const granted = false;

				shareOnFacebook = granted;
			} else {
				shareOnFacebook = false;
			}
		} catch (err) {
			shareOnFacebook = false;
		}

		this.setState({
			shareOnFacebook
		});
	};

	_handleLoading: Function = () => {
		this.setState({
			status: 'loading'
		});
	};

	_handlePosted: Function = thread => {
		this.props.onNavigation(new NavigationActions.Push({
			name: 'chat',
			props: {
				thread: thread.id,
				room: this.props.room.id
			}
		}));
	};

	_handleError: Function = message => {
		this.setState({
			error: message,
			status: null
		});
	};

	_postDiscussion: Function = () => {
		const SHORT_TITLE_MESSAGE = 'Name needs be at least 2 words';
		const LONG_TITLE_MESSAGE = 'Name needs be less than 10 words';
		const NO_TITLE_MESSAGE = 'Enter a title in 2 to 10 words';
		const NO_SUMMARY_MESSAGE = 'Enter a short summary';

		if (!this.state.name) {
			this._handleError(NO_TITLE_MESSAGE);
			return;
		}

		if (!this.state.body) {
			this._handleError(NO_SUMMARY_MESSAGE);
			return;
		}

		const words = this.state.name.trim().split(/\s+/);

		if (words.length < 2) {
			this._handleError(SHORT_TITLE_MESSAGE);
			return;
		} else if (words.length > 10) {
			this._handleError(LONG_TITLE_MESSAGE);
			return;
		}

		let meta;

		if (this.state.upload && this.state.meta && this.state.meta.photo) {
			const { upload } = this.state;
			const { height, width, title } = this.state.meta.photo;
			const aspectRatio = height / width;

			meta = {
				photo: {
					title,
					height,
					width,
					url: upload.originalUrl,
					thumbnail_height: Math.min(480, width) * aspectRatio,
					thumbnail_width: Math.min(480, width),
					thumbnail_url: upload.thumbnailUrl
				}
			};
		}

		this._handleLoading();

		this.props.startThread(
			this.state.name,
			this.state.body,
			meta
		);
	};

	_handlePress: Function = () => {
		if (this.state.status === 'loading') {
			return;
		}

		this._postDiscussion();
	};

	_handleChangeName: Function = name => {
		this.setState({
			name,
			error: null
		});
	};

	_handleChangeBody: Function = body => {
		this.setState({
			body,
			error: null
		});
	};

	_handleUploadImage: Function = async () => {
		try {
			this.setState({
				meta: {
					photo: null
				}
			});

			const imageData = await ImageChooser.pickImage();

			this.setState({
				meta: {
					photo: {
						title: imageData.name,
						height: imageData.height,
						width: imageData.width
					}
				}
			});
		} catch (e) {
			// Do nothing
		}
	};

	_handleUploadFinish: Function = upload => {
		this.setState({
			upload,
			error: null
		});
	};

	_handleUploadClose: Function = () => {
		this.setState({
			meta: {
				photo: null
			},
			upload: null,
			error: null
		});
	};

	render() {
		const isLoading = this.state.status === 'loading';
		const isDisabled = !(this.state.name && (this.state.body || this.state.meta && this.state.meta.photo) && !isLoading);

		return (
			<View style={styles.container}>
				<StatusbarWrapper />

				<AppbarSecondary>
					<AppbarTouchable type='secondary' onPress={this.props.dismiss}>
						<AppbarIcon name='close' style={styles.icon} />
					</AppbarTouchable>

					<AvatarContainer
						user={this.props.user}
						style={styles.userIcon}
						size={30}
					/>
				</AppbarSecondary>

				<Banner text={this.state.error} type='error' />

				<ScrollView style={styles.scene} keyboardShouldPersistTaps>
					<AppTextInput
						autoFocus
						value={this.state.name}
						onChangeText={this._handleChangeName}
						placeholder='Write a discussion title'
						autoCapitalize='sentences'
						style={[ styles.threadName, styles.entry ]}
						underlineColorAndroid='transparent'
					/>

					{this.state.imageData ?
						<ImageUploadContainer
							component={ImageUploadDiscussion}
							imageData={this.state.imageData}
							onUploadClose={this._handleUploadClose}
							onUploadFinish={this._handleUploadFinish}
							autoStart
						/> :
						<GrowingTextInput
							numberOfLines={5}
							value={this.state.body}
							onChangeText={this._handleChangeBody}
							placeholder='And a short summary'
							autoCapitalize='sentences'
							inputStyle={[ styles.threadSummary, styles.entry ]}
							underlineColorAndroid='transparent'
						/>
					}

					<TouchableOpacity onPress={this._handleSharePress}>
						<View style={styles.socialItem}>
							<View style={[ styles.socialIconContainer, this.state.shareOnFacebook ? styles.socialIconContainerSelected : null ]}>
								<EvilIcons name='sc-facebook' style={styles.socialIcon} />
							</View>
							<AppText style={[ styles.socialTextContainer, this.state.shareOnFacebook ? styles.socialTextSelected : null ]}>
								<AppText style={styles.socialLabel}>
									{this.state.shareOnFacebook ?
										'This will appear on your timeline\n' :
										'Tap to share this with your friends\n'
									}
								</AppText>
								<AppText style={styles.socialTip}>Tip: Sharing improves your discussion’s reach</AppText>
							</AppText>
						</View>
					</TouchableOpacity>
				</ScrollView>
				<View style={styles.footer}>
					<TouchFeedback
						borderless
						onPress={this._handleUploadImage}
					>
						<View style={styles.uploadButton}>
							<Icon
								name='image'
								style={[ styles.uploadButtonIcon, this.state.imageData ? styles.uploadButtonIconActive : null ]}
								size={24}
							/>
						</View>
					</TouchFeedback>
					<View style={[ styles.postButton, isDisabled ? styles.disabled : null ]}>
						{isDisabled ?
							<View style={styles.postButtonInner}>
								<AppText style={styles.postButtonText}>{isLoading ? 'Posting…' : 'Post'}</AppText>
							</View> :
							<TouchFeedback onPress={this._handlePress}>
								<View style={styles.postButtonInner}>
									<AppText style={styles.postButtonText}>Post</AppText>
								</View>
							</TouchFeedback>
						}
					</View>
				</View>
				<KeyboardSpacer />
			</View>
		);
	}
}
<commit_msg>Tweak the StartDiscussion component<commit_after>/* @flow */

import React, { Component, PropTypes } from 'react';
import ReactNative from 'react-native';
import EvilIcons from 'react-native-vector-icons/EvilIcons';
import Colors from '../../Colors';
import AppText from './AppText';
import AppTextInput from './AppTextInput';
import StatusbarWrapper from './StatusbarWrapper';
import AppbarSecondary from './AppbarSecondary';
import AppbarTouchable from './AppbarTouchable';
import AppbarIcon from './AppbarIcon';
import GrowingTextInput from './GrowingTextInput';
import TouchFeedback from './TouchFeedback';
import Icon from './Icon';
import AvatarContainer from '../containers/AvatarContainer';
import ImageUploadContainer from '../containers/ImageUploadContainer';
import Banner from './Banner';
import ImageUploadDiscussion from './ImageUploadDiscussion';
import KeyboardSpacer from './KeyboardSpacer';
import ImageChooser from '../../modules/ImageChooser';

const {
	AsyncStorage,
	TouchableOpacity,
	StyleSheet,
	NavigationActions,
	ScrollView,
	View
} = ReactNative;

const styles = StyleSheet.create({
	container: {
		flex: 1,
		backgroundColor: Colors.white
	},
	scene: {
		paddingHorizontal: 8,
		paddingVertical: 16
	},
	threadName: {
		fontWeight: 'bold',
		fontSize: 20,
		lineHeight: 30
	},
	threadSummary: {
		fontSize: 16,
		lineHeight: 24,
		margin: 0,
	},
	icon: {
		color: Colors.fadedBlack
	},
	disabled: {
		opacity: 0.5
	},
	userIcon: {
		margin: 12
	},
	entry: {
		paddingVertical: 4,
		paddingHorizontal: 12
	},
	uploadButtonIcon: {
		color: Colors.fadedBlack,
		marginHorizontal: 16,
		marginVertical: 14
	},
	uploadButtonIconActive: {
		color: Colors.info
	},
	footer: {
		flexDirection: 'row',
		justifyContent: 'space-between',
		borderTopColor: Colors.separator,
		borderTopWidth: 1
	},
	postButton: {
		backgroundColor: Colors.info,
		margin: 6,
		width: 100,
		borderRadius: 3
	},
	postButtonInner: {
		paddingVertical: 10,
		paddingHorizontal: 16
	},
	postButtonText: {
		color: Colors.white,
		fontWeight: 'bold',
		textAlign: 'center'
	},
	socialItem: {
		flexDirection: 'row',
		alignItems: 'center',
		margin: 12
	},
	socialIconContainer: {
		height: 28,
		width: 28,
		alignItems: 'center',
		justifyContent: 'center',
		backgroundColor: Colors.grey,
		borderRadius: 2
	},
	socialIconContainerSelected: {
		backgroundColor: Colors.facebook
	},
	socialIcon: {
		fontSize: 28,
		color: Colors.white
	},
	socialTextContainer: {
		marginHorizontal: 12,
		color: Colors.fadedBlack,
		bottom: -3
	},
	socialTextSelected: {
		color: Colors.facebook,
		fontWeight: 'bold'
	},
	socialLabel: {
		fontSize: 12,
		lineHeight: 18,
	},
	socialTip: {
		fontSize: 10,
		lineHeight: 15,
		opacity: 0.5,
		fontWeight: 'normal'
	}
});

const FACEBOOK_SHARE_CHECKED_KEY = 'start_discussion_facebook_share_checked';

type Props = {
	user: string;
	room: {
		id: string
	};
	dismiss: Function;
	startThread: Function;
	onNavigation: Function;
}

type State = {
	name: string;
	body: string;
	upload: ?{
		originalUrl: string;
		thumbnailUrl: string;
	};
	photo: ?{
		uri: string;
		size: number;
		name: string;
		height: number;
		width: number;
	};
	status: 'loading' | null;
	error: ?string;
	shareOnFacebook: boolean;
}

export default class StartDiscussionButton extends Component<void, Props, State> {
	static propTypes = {
		user: PropTypes.string.isRequired,
		room: PropTypes.shape({
			id: PropTypes.string.isRequired
		}).isRequired,
		dismiss: PropTypes.func.isRequired,
		startThread: PropTypes.func.isRequired,
		onNavigation: PropTypes.func.isRequired
	};

	state: State = {
		name: '',
		body: '',
		photo: null,
		upload: null,
		status: null,
		error: null,
		shareOnFacebook: false
	};

	componentDidMount() {
		this._setShareCheckbox();
	}

	_handleSharePress: Function = () => {
		global.requestAnimationFrame(async () => {
			let shareOnFacebook = !this.state.shareOnFacebook;

			if (shareOnFacebook) {
				try {
					// await this.props.requestFacebookPermissions();
				} catch (err) {
					shareOnFacebook = false;
				}
			}

			this.setState({
				shareOnFacebook
			});

			AsyncStorage.setItem(FACEBOOK_SHARE_CHECKED_KEY, JSON.stringify(shareOnFacebook));
		});
	};

	_setShareCheckbox: Function = async (): Promise<any> => {
		let shareOnFacebook;

		try {
			const isEnabled = JSON.parse(await AsyncStorage.getItem(FACEBOOK_SHARE_CHECKED_KEY));

			if (isEnabled) {
				// const granted = await this.props.isFacebookPermissionGranted();
				const granted = false;

				shareOnFacebook = granted;
			} else {
				shareOnFacebook = false;
			}
		} catch (err) {
			shareOnFacebook = false;
		}

		this.setState({
			shareOnFacebook
		});
	};

	_handleLoading: Function = () => {
		this.setState({
			status: 'loading'
		});
	};

	_handlePosted: Function = thread => {
		this.props.onNavigation(new NavigationActions.Push({
			name: 'chat',
			props: {
				thread: thread.id,
				room: this.props.room.id
			}
		}));
	};

	_handleError: Function = message => {
		this.setState({
			error: message,
			status: null
		});
	};

	_postDiscussion: Function = () => {
		const SHORT_TITLE_MESSAGE = 'Name needs be at least 2 words';
		const LONG_TITLE_MESSAGE = 'Name needs be less than 10 words';
		const NO_TITLE_MESSAGE = 'Enter a title in 2 to 10 words';
		const NO_SUMMARY_MESSAGE = 'Enter a short summary';

		if (!this.state.name) {
			this._handleError(NO_TITLE_MESSAGE);
			return;
		}

		if (!this.state.body) {
			this._handleError(NO_SUMMARY_MESSAGE);
			return;
		}

		const words = this.state.name.trim().split(/\s+/);

		if (words.length < 2) {
			this._handleError(SHORT_TITLE_MESSAGE);
			return;
		} else if (words.length > 10) {
			this._handleError(LONG_TITLE_MESSAGE);
			return;
		}

		let meta;

		if (this.state.upload && this.state.photo) {
			const { upload } = this.state;
			const { height, width, name } = this.state.photo;
			const aspectRatio = height / width;

			meta = {
				photo: {
					height,
					width,
					title: name,
					url: upload.originalUrl,
					thumbnail_height: Math.min(480, width) * aspectRatio,
					thumbnail_width: Math.min(480, width),
					thumbnail_url: upload.thumbnailUrl
				}
			};
		}

		this._handleLoading();

		this.props.startThread(
			this.state.name,
			this.state.body,
			meta
		);
	};

	_handlePress: Function = () => {
		if (this.state.status === 'loading') {
			return;
		}

		this._postDiscussion();
	};

	_handleChangeName: Function = name => {
		this.setState({
			name,
			error: null
		});
	};

	_handleChangeBody: Function = body => {
		this.setState({
			body,
			error: null
		});
	};

	_handleUploadImage: Function = async () => {
		try {
			this.setState({
				photo: null
			});

			const photo = await ImageChooser.pickImage();

			this.setState({
				photo
			});
		} catch (e) {
			// Do nothing
		}
	};

	_handleUploadFinish: Function = upload => {
		this.setState({
			upload,
			error: null
		});
	};

	_handleUploadClose: Function = () => {
		this.setState({
			photo: null,
			upload: null,
			error: null
		});
	};

	render() {
		const isLoading = this.state.status === 'loading';
		const isDisabled = !(this.state.name && (this.state.body || this.state.meta && this.state.meta.photo) && !isLoading);

		return (
			<View style={styles.container}>
				<StatusbarWrapper />

				<AppbarSecondary>
					<AppbarTouchable type='secondary' onPress={this.props.dismiss}>
						<AppbarIcon name='close' style={styles.icon} />
					</AppbarTouchable>

					<AvatarContainer
						user={this.props.user}
						style={styles.userIcon}
						size={30}
					/>
				</AppbarSecondary>

				<Banner text={this.state.error} type='error' />

				<ScrollView style={styles.scene} keyboardShouldPersistTaps>
					<AppTextInput
						autoFocus
						value={this.state.name}
						onChangeText={this._handleChangeName}
						placeholder='Write a discussion title'
						autoCapitalize='sentences'
						style={[ styles.threadName, styles.entry ]}
						underlineColorAndroid='transparent'
					/>

					{this.state.photo ?
						<ImageUploadContainer
							component={ImageUploadDiscussion}
							imageData={this.state.photo}
							onUploadClose={this._handleUploadClose}
							onUploadFinish={this._handleUploadFinish}
							autoStart
						/> :
						<GrowingTextInput
							numberOfLines={5}
							value={this.state.body}
							onChangeText={this._handleChangeBody}
							placeholder='And a short summary'
							autoCapitalize='sentences'
							inputStyle={[ styles.threadSummary, styles.entry ]}
							underlineColorAndroid='transparent'
						/>
					}

					<TouchableOpacity onPress={this._handleSharePress}>
						<View style={styles.socialItem}>
							<View style={[ styles.socialIconContainer, this.state.shareOnFacebook ? styles.socialIconContainerSelected : null ]}>
								<EvilIcons name='sc-facebook' style={styles.socialIcon} />
							</View>
							<AppText style={[ styles.socialTextContainer, this.state.shareOnFacebook ? styles.socialTextSelected : null ]}>
								<AppText style={styles.socialLabel}>
									{this.state.shareOnFacebook ?
										'This will appear on your timeline\n' :
										'Tap to share this with your friends\n'
									}
								</AppText>
								<AppText style={styles.socialTip}>Tip: Sharing improves your discussion’s reach</AppText>
							</AppText>
						</View>
					</TouchableOpacity>
				</ScrollView>
				<View style={styles.footer}>
					<TouchFeedback
						borderless
						onPress={this._handleUploadImage}
					>
						<View style={styles.uploadButton}>
							<Icon
								name='image'
								style={[ styles.uploadButtonIcon, this.state.photo ? styles.uploadButtonIconActive : null ]}
								size={24}
							/>
						</View>
					</TouchFeedback>
					<View style={[ styles.postButton, isDisabled ? styles.disabled : null ]}>
						{isDisabled ?
							<View style={styles.postButtonInner}>
								<AppText style={styles.postButtonText}>{isLoading ? 'Posting…' : 'Post'}</AppText>
							</View> :
							<TouchFeedback onPress={this._handlePress}>
								<View style={styles.postButtonInner}>
									<AppText style={styles.postButtonText}>Post</AppText>
								</View>
							</TouchFeedback>
						}
					</View>
				</View>
				<KeyboardSpacer />
			</View>
		);
	}
}
<|endoftext|>


<commit_before>'use strict';

angular.module('app').directive('codemirror', [
  '$window',
  '$timeout',
  function($window, $timeout) {
    return {
      restrict: 'A',
      require: 'ngModel',
      scope: {
        codemirror: '=',
        hasFocus: '=',
        handle: '=',
        customData: '='
      },
      link: (scope, element, attrs, ngModelCtrl) => {
        let editor;
        let options = scope.codemirror || {};

        scope.handle = scope.handle || {};
        scope.handle.autoformat = () => {
          _autoFormatSelection(editor);
        };
        scope.handle.refresh = () => {
          _refresh(editor);
        };

        const TAB = '  '; //2 spaces

        options.lineNumbers = options.lineNumbers || true;
        options.extraKeys = options.extraKeys || {};
        options.tabSize = TAB.length;
        options.indentWithTabs = false;
        options.mode = {
          name: 'javascript',
          globalVars: true
        };

        init();

        //take initial model value and set editor with it
        ngModelCtrl.$formatters.push((modelValue) => {
          $timeout(() => {
            editor.setValue(modelValue);
          });
          return modelValue;
        });

        function init() {
          editor = new $window.CodeMirror((editorElement) => {
            element.append(editorElement);
          }, options);

          _.extend(editor, {
            customData: scope.customData
          });

          element.data('CodeMirrorInstance', editor); //make the instance available from the DOM

          editor.setOption('extraKeys', {
            Tab: (cm) => { //use spaces instead of tabs
              let spaces = new Array(cm.getOption('indentUnit') + 1).join(' ');
              cm.replaceSelection(spaces);
            }
          });

          _registerEditorEvents();

          $timeout(() => {
            _refresh(editor);
            $timeout(() => { //TODO: figure out a better way - sometimes the editor styles are screwed up, re-freshing helps but this isn't ideal
              _refresh(editor);
            }, 500);
          });
        }

        function _autoFormatSelection(codeMirrorEditor) {
          if (!codeMirrorEditor) return;

          let totalLines = codeMirrorEditor.lineCount();
          let totalChars = codeMirrorEditor.getValue().length;

          codeMirrorEditor.autoFormatRange({
            line: 0,
            ch: 0
          }, {
            line: totalLines,
            ch: totalChars
          });
        }

        function _showAutoComplete(cm, event) {
          if (event && (event.keyIdentifier.indexOf('Up') !== -1 || event.keyIdentifier.indexOf('Down') !== -1)) return;

          CodeMirror.commands.autocomplete(cm, null, {
            completeSingle: false
          });
        }

        function _refresh(codeMirrorEditor) {
          codeMirrorEditor.refresh();
        }

        function _registerEditorEvents() {
          editor.on('keyup', (cm, event) => {
            $timeout(() => {
              _showAutoComplete(cm, event);
            });
          });

          editor.on('change', () => {
            $timeout(() => {
              var value = editor.getValue();
              value = value && value.trim ? value.trim() : value;

              ngModelCtrl.$setViewValue(value);
            });
          });

          editor.on('focus', (cm) => {
            $timeout(() => {
              scope.hasFocus = true;

              var value = editor.getValue();

              if (!value) {
                _showAutoComplete(cm);
              }
            });
          });

          editor.on('blur', () => {
            $timeout(() => {
              scope.hasFocus = false;
            });
          });
        }
      }
    };
  }
]);

(function() {
  const automcomplete = require('lib/modules/automcomplete');

  CodeMirror.registerHelper('hint', 'javascript', (codemirror) => {
    let cursor = codemirror.getCursor();
    let currentExpression = codemirror.getValue();
    let currentWordRange = codemirror.findWordAt(cursor);
    let currentWord = codemirror.getRange(currentWordRange.anchor, currentWordRange.head).replace(/[^\w\s]/gi, '');
    let customData = codemirror.customData || [];

    let results = automcomplete.getHintsByValue(currentExpression, currentWord, {
      collectionNames: customData.collectionNames
    });

    let inner = {
      from: cursor,
      to: cursor,
      currentWord: currentWord,
      list: _filterAutoCompleteHintsByInput(results.value, results.hints || []) || []
    };

    return inner;
  });

  // https://github.com/codemirror/CodeMirror/issues/3092
  let javascriptHint = CodeMirror.hint.javascript;
  CodeMirror.hint.javascript = (codemirror, options) => {
    let codemirrorInstance = codemirror;

    let result = javascriptHint(codemirror, options);

    if (result) {
      CodeMirror.on(result, 'pick', (selectedHint) => {
        let cursor = codemirrorInstance.getCursor();
        let currentValue = codemirrorInstance.getValue();
        let newValue = currentValue.replace(result.currentWord + selectedHint, selectedHint);

        codemirrorInstance.setValue(newValue);

        let newChar = newValue.length;
        codemirrorInstance.setCursor(cursor.line, newChar);
      });
    }
    return result;
  };

  function _filterAutoCompleteHintsByInput(input, hints) {
    if (typeof input !== 'string') return null;
    if (!hints || !hints.length) return null;

    var term = $.ui.autocomplete.escapeRegex(input);

    var startsWithMatcher = new RegExp('^' + term, 'i');
    var startsWith = $.grep(hints, function(value) {
      return startsWithMatcher.test(value.label || value.value || value);
    });

    var containsMatcher = new RegExp(term, 'i');
    var contains = $.grep(hints, function(value) {
      return $.inArray(value, startsWith) < 0 &&
        containsMatcher.test(value.label || value.value || value);
    });

    return startsWith.concat(contains);
  }
}());
<commit_msg>use the old default javascript hint implementation<commit_after>'use strict';

angular.module('app').directive('codemirror', [
  '$window',
  '$timeout', ($window, $timeout) => {
    return {
      restrict: 'A',
      require: 'ngModel',
      scope: {
        codemirror: '=',
        hasFocus: '=',
        handle: '=',
        customData: '='
      },
      link: (scope, element, attrs, ngModelCtrl) => {
        let editor;
        let options = scope.codemirror || {};

        scope.handle = scope.handle || {};
        scope.handle.autoformat = () => {
          _autoFormatSelection(editor);
        };
        scope.handle.refresh = () => {
          _refresh(editor);
        };

        const TAB = '  '; //2 spaces

        options.lineNumbers = options.lineNumbers || true;
        options.extraKeys = options.extraKeys || {};
        options.tabSize = TAB.length;
        options.indentWithTabs = false;
        options.autofocus = true;
        options.mode = {
          name: 'javascript',
          globalVars: true
        };

        init();

        //take initial model value and set editor with it
        ngModelCtrl.$formatters.push((modelValue) => {
          $timeout(() => {
            editor.setValue(modelValue);
          });
          return modelValue;
        });

        function init() {
          editor = new $window.CodeMirror((editorElement) => {
            element.append(editorElement);
          }, options);

          _.extend(editor, {
            customData: scope.customData
          });

          element.data('CodeMirrorInstance', editor); //make the instance available from the DOM

          editor.setOption('extraKeys', {
            Tab: (cm) => { //use spaces instead of tabs
              let spaces = new Array(cm.getOption('indentUnit') + 1).join(' ');
              cm.replaceSelection(spaces);
            }
          });

          _registerEditorEvents();

          $timeout(() => {
            _refresh(editor);
            $timeout(() => { //TODO: figure out a better way - sometimes the editor styles are screwed up, re-freshing helps but this isn't ideal
              _refresh(editor);
            }, 500);
          });
        }

        function _autoFormatSelection(codeMirrorEditor) {
          if (!codeMirrorEditor) return;

          let totalLines = codeMirrorEditor.lineCount();
          let totalChars = codeMirrorEditor.getValue().length;

          codeMirrorEditor.autoFormatRange({
            line: 0,
            ch: 0
          }, {
            line: totalLines,
            ch: totalChars
          });
        }

        function _showAutoComplete(cm, event) {
          if (event && (event.keyIdentifier.indexOf('Up') !== -1 || event.keyIdentifier.indexOf('Down') !== -1)) return;

          CodeMirror.commands.autocomplete(cm, null, {
            completeSingle: false
          });
        }

        function _refresh(codeMirrorEditor) {
          codeMirrorEditor.refresh();
        }

        function _registerEditorEvents() {
          editor.on('keyup', (cm, event) => {
            if (!cm.state.completionActive && (event.keyCode !== 13 && event.keyCode !== 27)) {
              CodeMirror.commands.autocomplete(cm, null, {
                completeSingle: false
              });
            }
          });

          editor.on('change', () => {
            $timeout(() => {
              var value = editor.getValue();
              value = value && value.trim ? value.trim() : value;

              ngModelCtrl.$setViewValue(value);
            });
          });

          editor.on('focus', (cm) => {
            $timeout(() => {
              scope.hasFocus = true;

              var value = editor.getValue();

              if (!value) {
                _showAutoComplete(cm);
              }
            });
          });

          editor.on('blur', () => {
            $timeout(() => {
              scope.hasFocus = false;
            });
          });
        }
      }
    };
  }
]);

// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
  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;
    let context = [];
    // If it is a property, find out what it is a property of.
    while (tprop.type === "property") {
      tprop = getToken(editor, new Pos(cur.line, tprop.start));
      if (tprop.string !== ".") return;
      tprop = getToken(editor, new Pos(cur.line, tprop.start));
      if (!context) context = [];
      context.push(tprop);
    }

    options.globalScope = {
      db: {}
    };
    options.additionalContext = {
      db: {
        Cars: {
          find: () => {},
          aggregate: () => {},
          count: () => {}
        }
      }
    };

    return {
      list: token.string ? getCompletions(token, context, keywords, options) : [],
      from: new Pos(cur.line, token.start),
      to: new 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);

  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(" ");

  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 (let v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
      for (let v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
      if (!options || options.useGlobalScope !== false)
        gatherCompletions(global);
      forEach(keywords, maybeAdd);
    }
    return found;
  }
});
<|endoftext|>


<commit_before>/*==================================================
 *  Exhibit.FormatParser
 *  http://simile.mit.edu/wiki/Exhibit/API/FormatParser
 *==================================================
 */
Exhibit.FormatParser = new Object();

Exhibit.FormatParser.parse = function(uiContext, s, startIndex, results) {
    startIndex = startIndex || 0;
    results = results || {};

    var scanner = new Exhibit.FormatScanner(s, startIndex);
    try {
        return Exhibit.FormatParser._internalParse(uiContext, scanner, results, false);
    } finally {
        results.index = scanner.token() != null ? scanner.token().start : scanner.index();
    }
};

Exhibit.FormatParser.parseSeveral = function(uiContext, s, startIndex, results) {
    startIndex = startIndex || 0;
    results = results || {};

    var scanner = new Exhibit.FormatScanner(s, startIndex);
    try {
        return Exhibit.FormatParser._internalParse(uiContext, scanner, results, true);
    } finally {
        results.index = scanner.token() != null ? scanner.token().start : scanner.index();
    }
};

Exhibit.FormatParser._valueTypes = {
    "list" : true,
    "number" : true,
    "date" : true,
    "item" : true,
    "text" : true,
    "url" : true,
    "image" : true,
    "currency" : true
};

Exhibit.FormatParser._internalParse = function(uiContext, scanner, results, several) {
    var Scanner = Exhibit.FormatScanner;
    var token = scanner.token();
    var next = function() { scanner.next(); token = scanner.token(); };
    var makePosition = function() { return token != null ? token.start : scanner.index(); };
    var enterSetting = function(valueType, settingName, value) {
        uiContext.putSetting("format/" + valueType + "/" + settingName, value);
    };
    var checkKeywords = function(valueType, settingName, keywords) {
        if (token != null && token.type != Scanner.IDENTIFIER && token.value in keywords) {
            enterSetting(valueType, settingName, keywords[token.value]);
            next();
            return false;
        }
        return true;
    };

    var parseNumber = function(valueType, settingName, keywords) {
        if (checkKeywords(valueType, settingName, keywords)) {
            if (token == null || token.type != Scanner.NUMBER) {
                throw new Error("Missing number at position " + makePosition());
            }
            enterSetting(valueType, settingName, token.value);
            next();
        }
    };
    var parseInteger = function(valueType, settingName, keywords) {
        if (checkKeywords(valueType, settingName, keywords)) {
            if (token == null || token.type != Scanner.NUMBER) {
                throw new Error("Missing integer at position " + makePosition());
            }
            enterSetting(valueType, settingName, Math.round(token.value));
            next();
        }
    };
    var parseNonnegativeInteger = function(valueType, settingName, keywords) {
        if (checkKeywords(valueType, settingName, keywords)) {
            if (token == null || token.type != Scanner.NUMBER || token.value < 0) {
                throw new Error("Missing non-negative integer at position " + makePosition());
            }
            enterSetting(valueType, settingName, Math.round(token.value));
            next();
        }
    };
    var parseString = function(valueType, settingName, keywords) {
        if (checkKeywords(valueType, settingName, keywords)) {
            if (token == null || token.type != Scanner.STRING) {
                throw new Error("Missing string at position " + makePosition());
            }
            enterSetting(valueType, settingName, token.value);
            next();
        }
    };
    var parseURL = function(valueType, settingName, keywords) {
        if (checkKeywords(valueType, settingName, keywords)) {
            if (token == null || token.type != Scanner.URL) {
                throw new Error("Missing url at position " + makePosition());
            }
            enterSetting(valueType, settingName, token.value);
            next();
        }
    };
    var parseExpression = function(valueType, settingName, keywords) {
        if (checkKeywords(valueType, settingName, keywords)) {
            if (token == null || token.type != Scanner.EXPRESSION) {
                throw new Error("Missing expression at position " + makePosition());
            }
            enterSetting(valueType, settingName, token.value);
            next();
        }
    };
    var parseExpressionOrString = function(valueType, settingName, keywords) {
        if (checkKeywords(valueType, settingName, keywords)) {
            if (token == null || (token.type != Scanner.EXPRESSION && token.type != Scanner.STRING)) {
                throw new Error("Missing expression or string at position " + makePosition());
            }
            enterSetting(valueType, settingName, token.value);
            next();
        }
    };
    var parseChoices = function(valueType, settingName, choices) {
        if (token == null || token.type != Scanner.IDENTIFIER) {
            throw new Error("Missing option at position " + makePosition());
        }
        for (var i = 0; i < choices.length; i++) {
            if (token.value == choices[i]) {
                enterSetting(valueType, settingName, token.value);
                next();
                return;
            }
        }
        throw new Error(
            "Unsupported option " + token.value +
            " for setting " + settingName +
            " on value type " + valueType +
            " found at position " + makePosition());
    };
    var parseFlags = function(valueType, settingName, flags, counterFlags) {
        outer: while (token != null && token.type == Scanner.IDENTIFIER) {
            for (var i = 0; i < flags.length; i++) {
                if (token.value == flags[i]) {
                    enterSetting(valueType, settingName + "/" + token.value, true);
                    next();
                    continue outer;
                }
            }
            if (token.value in counterFlags) {
                enterSetting(valueType, settingName + "/" + counterFlags[token.value], false);
                next();
                continue outer;
            }
            throw new Error(
                "Unsupported flag " + token.value +
                " for setting " + settingName +
                " on value type " + valueType +
                " found at position " + makePosition());
        }
    };

    var parseSetting = function(valueType, settingName) {
        switch (valueType) {
        case "number":
            switch (settingName) {
            case "decimal-digits":
                parseNonnegativeInteger(valueType, settingName, { "default": -1 });
                return;
            }
            break;
        case "date":
            switch (settingName) {
            case "time-zone":
                parseNumber(valueType, settingName, { "default" : null });
                return;
            case "show":
                parseChoices(valueType, settingName, [ "date", "time", "date-time" ]);
                return;
            case "mode":
                parseChoices(valueType, settingName, [ "short", "medium", "long", "full" ]);
                enterSetting(valueType, "template", null); // mode and template are exclusive
                return;
            case "template":
                parseString(valueType, settingName, {});
                enterSetting(valueType, "mode", null); // mode and template are exclusive
                return;
            }
            break;
        case "boolean":
            switch (settingName) {
            }
            break;
        case "text":
            switch (settingName) {
            case "max-length":
                parseInteger(valueType, settingName, { "none" : 0 });
                return;
            }
            break;
        case "image":
            switch (settingName) {
            case "tooltip":
                parseExpressionOrString(valueType, settingName, { "none" : null });
                return;
            case "max-width":
            case "max-height":
                parseInteger(valueType, settingName, { "none" : -1 });
                return;
            }
            break;
        case "url":
            switch (settingName) {
            case "target":
                parseString(valueType, settingName, { "none" : null });
                return;
            case "external-icon":
                parseURL(valueType, settingName, { "none" : null });
                return;
            }
            break;
        case "item":
            switch (settingName) {
            case "title":
                parseExpression(valueType, settingName, { "default" : null });
                return;
            }
            break;
        case "currency":
            switch (settingName) {
            case "negative-format":
                parseFlags(valueType, settingName,
                    [ "red", "parentheses", "signed" ],
                    { "unsigned" : "signed", "no-parenthesis" : "parentheses", "black" : "red" }
                );
                return;
            case "symbol":
                parseString(valueType, settingName, { "default" : "$", "none" : null });
                return;
            case "symbol-placement":
                parseChoices(valueType, settingName, [ "first", "last", "after-sign" ]);
                return;
            case "decimal-digits":
                parseNonnegativeInteger(valueType, settingName);
                return;
            }
            break;
        case "list":
            switch (settingName) {
            case "separator":
            case "last-separator":
            case "pair-separator":
            case "empty-text":
                parseString(valueType, settingName);
                return;
            }
            break;
        }
        throw new Error("Unsupported setting called " + settingName +
            " for value type " + valueType + " found at position " + makePosition());
    };
    var parseSettingList = function(valueType) {

        while (token != null && token.type == Scanner.IDENTIFIER) {
            var settingName = token.value;

            next();

            if (token == null || token.type != Scanner.DELIMITER || token.value != ":") {
                throw new Error("Missing : at position " + makePosition());
            }
            next();

            parseSetting(valueType, settingName);

            if (token == null || token.type != Scanner.DELIMITER || token.value != ";") {
                break;
            } else {
                next();
            }
        }

    }
    var parseRule = function() {
        if (token == null || token.type != Scanner.IDENTIFIER) {
            throw new Error("Missing value type at position " + makePosition());
        }

        var valueType = token.value;
        if (!(valueType in Exhibit.FormatParser._valueTypes)) {
            throw new Error("Unsupported value type " + valueType + " at position " + makePosition());
        }
        next();

        if (token != null && token.type == Scanner.DELIMITER && token.value == "{") {
            next();
            parseSettingList(valueType);

            if (token == null || token.type != Scanner.DELIMITER || token.value != "}") {
                throw new Error("Missing } at position " + makePosition());
            }
            next();
        }
        return valueType;
    };
    var parseRuleList = function() {
        var valueType = "text";
        while (token != null && token.type == Scanner.IDENTIFIER) {
            valueType = parseRule();
        }
        return valueType;
    }

    if (several) {
        return parseRuleList();
    } else {
        return parseRule();
    }
};

/*==================================================
 *  Exhibit.FormatScanner
 *==================================================
 */
Exhibit.FormatScanner = function(text, startIndex) {
    this._text = text + " "; // make it easier to parse
    this._maxIndex = text.length;
    this._index = startIndex;
    this.next();
};

Exhibit.FormatScanner.DELIMITER     = 0;
Exhibit.FormatScanner.NUMBER        = 1;
Exhibit.FormatScanner.STRING        = 2;
Exhibit.FormatScanner.IDENTIFIER    = 3;
Exhibit.FormatScanner.URL           = 4;
Exhibit.FormatScanner.EXPRESSION    = 5;
Exhibit.FormatScanner.COLOR         = 6;

Exhibit.FormatScanner.prototype.token = function() {
    return this._token;
};

Exhibit.FormatScanner.prototype.index = function() {
    return this._index;
};

Exhibit.FormatScanner.prototype.next = function() {
    this._token = null;

    var self = this;
    var skipSpaces = function(x) {
        while (x < self._maxIndex &&
            " \t\r\n".indexOf(self._text.charAt(x)) >= 0) {

            x++;
        }
        return x;
    };
    this._index = skipSpaces(this._index);

    if (this._index < this._maxIndex) {
        var c1 = this._text.charAt(this._index);
        var c2 = this._text.charAt(this._index + 1);

        if ("{}(),:;".indexOf(c1) >= 0) {
            this._token = {
                type:   Exhibit.FormatScanner.DELIMITER,
                value:  c1,
                start:  this._index,
                end:    this._index + 1
            };
            this._index++;
        } else if ("\"'".indexOf(c1) >= 0) { // quoted strings
            var i = this._index + 1;
            while (i < this._maxIndex) {
                if (this._text.charAt(i) == c1 && this._text.charAt(i - 1) != "\\") {
                    break;
                }
                i++;
            }

            if (i < this._maxIndex) {
                this._token = {
                    type:   Exhibit.FormatScanner.STRING,
                    value:  this._text.substring(this._index + 1, i).replace(/\\'/g, "'").replace(/\\"/g, '"'),
                    start:  this._index,
                    end:    i + 1
                };
                this._index = i + 1;
            } else {
                throw new Error("Unterminated string starting at " + this._index);
            }
        } else if (c1 == "#") { // color
            var i = this._index + 1;
            while (i < this._maxIndex && this._isHexDigit(this._text.charAt(i))) {
                i++;
            }

            this._token = {
                type:   Exhibit.FormatScanner.COLOR,
                value:  this._text.substring(this._index, i),
                start:  this._index,
                end:    i
            };
            this._index = i;
        } else if (this._isDigit(c1)) { // number
            var i = this._index;
            while (i < this._maxIndex && this._isDigit(this._text.charAt(i))) {
                i++;
            }

            if (i < this._maxIndex && this._text.charAt(i) == ".") {
                i++;
                while (i < this._maxIndex && this._isDigit(this._text.charAt(i))) {
                    i++;
                }
            }

            this._token = {
                type:   Exhibit.FormatScanner.NUMBER,
                value:  parseFloat(this._text.substring(this._index, i)),
                start:  this._index,
                end:    i
            };
            this._index = i;
        } else { // identifier
            var i = this._index;
            while (i < this._maxIndex) {
                var j = this._text.substr(i).search(/\W/);
                if (j > 0) {
                    i += j;
                } else if ("-".indexOf(this._text.charAt(i)) >= 0) {
                    i++;
                } else {
                    break;
                }
            }

            var identifier = this._text.substring(this._index, i);
            while (true) {
                if (identifier == "url") {
                    var openParen = skipSpaces(i);
                    if (this._text.charAt(openParen) == "(") {
                        var closeParen = this._text.indexOf(")", openParen);
                        if (closeParen > 0) {
                            this._token = {
                                type:   Exhibit.FormatScanner.URL,
                                value:  this._text.substring(openParen + 1, closeParen),
                                start:  this._index,
                                end:    closeParen + 1
                            };
                            this._index = closeParen + 1;
                            break;
                        } else {
                            throw new Error("Missing ) to close url at " + this._index);
                        }
                    }
                } else if (identifier == "expression") {
                    var openParen = skipSpaces(i);
                    if (this._text.charAt(openParen) == "(") {
                        var o = {};
                        var expression = Exhibit.ExpressionParser.parse(this._text, openParen + 1, o);

                        var closeParen = skipSpaces(o.index);
                        if (this._text.charAt(closeParen) == ")") {
                            this._token = {
                                type:   Exhibit.FormatScanner.EXPRESSION,
                                value:  expression,
                                start:  this._index,
                                end:    closeParen + 1
                            };
                            this._index = closeParen + 1;
                            break;
                        } else {
                            throw new Error("Missing ) to close expression at " + o.index);
                        }
                    }
                }

                this._token = {
                    type:   Exhibit.FormatScanner.IDENTIFIER,
                    value:  identifier,
                    start:  this._index,
                    end:    i
                };
                this._index = i;
                break;
            }
        }
    }
};

Exhibit.FormatScanner.prototype._isDigit = function(c) {
    return "0123456789".indexOf(c) >= 0;
};
Exhibit.FormatScanner.prototype._isHexDigit = function(c) {
    return "0123456789abcdefABCDEF".indexOf(c) >= 0;
};
<commit_msg>Minor bugs in format parser (missing the keywords parameter in a few calls in untested code).<commit_after>/*==================================================
 *  Exhibit.FormatParser
 *  http://simile.mit.edu/wiki/Exhibit/API/FormatParser
 *==================================================
 */
Exhibit.FormatParser = new Object();

Exhibit.FormatParser.parse = function(uiContext, s, startIndex, results) {
    startIndex = startIndex || 0;
    results = results || {};

    var scanner = new Exhibit.FormatScanner(s, startIndex);
    try {
        return Exhibit.FormatParser._internalParse(uiContext, scanner, results, false);
    } finally {
        results.index = scanner.token() != null ? scanner.token().start : scanner.index();
    }
};

Exhibit.FormatParser.parseSeveral = function(uiContext, s, startIndex, results) {
    startIndex = startIndex || 0;
    results = results || {};

    var scanner = new Exhibit.FormatScanner(s, startIndex);
    try {
        return Exhibit.FormatParser._internalParse(uiContext, scanner, results, true);
    } finally {
        results.index = scanner.token() != null ? scanner.token().start : scanner.index();
    }
};

Exhibit.FormatParser._valueTypes = {
    "list" : true,
    "number" : true,
    "date" : true,
    "item" : true,
    "text" : true,
    "url" : true,
    "image" : true,
    "currency" : true
};

Exhibit.FormatParser._internalParse = function(uiContext, scanner, results, several) {
    var Scanner = Exhibit.FormatScanner;
    var token = scanner.token();
    var next = function() { scanner.next(); token = scanner.token(); };
    var makePosition = function() { return token != null ? token.start : scanner.index(); };
    var enterSetting = function(valueType, settingName, value) {
        uiContext.putSetting("format/" + valueType + "/" + settingName, value);
    };
    var checkKeywords = function(valueType, settingName, keywords) {
        if (token != null && token.type != Scanner.IDENTIFIER && token.value in keywords) {
            enterSetting(valueType, settingName, keywords[token.value]);
            next();
            return false;
        }
        return true;
    };

    var parseNumber = function(valueType, settingName, keywords) {
        if (checkKeywords(valueType, settingName, keywords)) {
            if (token == null || token.type != Scanner.NUMBER) {
                throw new Error("Missing number at position " + makePosition());
            }
            enterSetting(valueType, settingName, token.value);
            next();
        }
    };
    var parseInteger = function(valueType, settingName, keywords) {
        if (checkKeywords(valueType, settingName, keywords)) {
            if (token == null || token.type != Scanner.NUMBER) {
                throw new Error("Missing integer at position " + makePosition());
            }
            enterSetting(valueType, settingName, Math.round(token.value));
            next();
        }
    };
    var parseNonnegativeInteger = function(valueType, settingName, keywords) {
        if (checkKeywords(valueType, settingName, keywords)) {
            if (token == null || token.type != Scanner.NUMBER || token.value < 0) {
                throw new Error("Missing non-negative integer at position " + makePosition());
            }
            enterSetting(valueType, settingName, Math.round(token.value));
            next();
        }
    };
    var parseString = function(valueType, settingName, keywords) {
        if (checkKeywords(valueType, settingName, keywords)) {
            if (token == null || token.type != Scanner.STRING) {
                throw new Error("Missing string at position " + makePosition());
            }
            enterSetting(valueType, settingName, token.value);
            next();
        }
    };
    var parseURL = function(valueType, settingName, keywords) {
        if (checkKeywords(valueType, settingName, keywords)) {
            if (token == null || token.type != Scanner.URL) {
                throw new Error("Missing url at position " + makePosition());
            }
            enterSetting(valueType, settingName, token.value);
            next();
        }
    };
    var parseExpression = function(valueType, settingName, keywords) {
        if (checkKeywords(valueType, settingName, keywords)) {
            if (token == null || token.type != Scanner.EXPRESSION) {
                throw new Error("Missing expression at position " + makePosition());
            }
            enterSetting(valueType, settingName, token.value);
            next();
        }
    };
    var parseExpressionOrString = function(valueType, settingName, keywords) {
        if (checkKeywords(valueType, settingName, keywords)) {
            if (token == null || (token.type != Scanner.EXPRESSION && token.type != Scanner.STRING)) {
                throw new Error("Missing expression or string at position " + makePosition());
            }
            enterSetting(valueType, settingName, token.value);
            next();
        }
    };
    var parseChoices = function(valueType, settingName, choices) {
        if (token == null || token.type != Scanner.IDENTIFIER) {
            throw new Error("Missing option at position " + makePosition());
        }
        for (var i = 0; i < choices.length; i++) {
            if (token.value == choices[i]) {
                enterSetting(valueType, settingName, token.value);
                next();
                return;
            }
        }
        throw new Error(
            "Unsupported option " + token.value +
            " for setting " + settingName +
            " on value type " + valueType +
            " found at position " + makePosition());
    };
    var parseFlags = function(valueType, settingName, flags, counterFlags) {
        outer: while (token != null && token.type == Scanner.IDENTIFIER) {
            for (var i = 0; i < flags.length; i++) {
                if (token.value == flags[i]) {
                    enterSetting(valueType, settingName + "/" + token.value, true);
                    next();
                    continue outer;
                }
            }
            if (token.value in counterFlags) {
                enterSetting(valueType, settingName + "/" + counterFlags[token.value], false);
                next();
                continue outer;
            }
            throw new Error(
                "Unsupported flag " + token.value +
                " for setting " + settingName +
                " on value type " + valueType +
                " found at position " + makePosition());
        }
    };

    var parseSetting = function(valueType, settingName) {
        switch (valueType) {
        case "number":
            switch (settingName) {
            case "decimal-digits":
                parseNonnegativeInteger(valueType, settingName, { "default": -1 });
                return;
            }
            break;
        case "date":
            switch (settingName) {
            case "time-zone":
                parseNumber(valueType, settingName, { "default" : null });
                return;
            case "show":
                parseChoices(valueType, settingName, [ "date", "time", "date-time" ]);
                return;
            case "mode":
                parseChoices(valueType, settingName, [ "short", "medium", "long", "full" ]);
                enterSetting(valueType, "template", null); // mode and template are exclusive
                return;
            case "template":
                parseString(valueType, settingName, {});
                enterSetting(valueType, "mode", null); // mode and template are exclusive
                return;
            }
            break;
        case "boolean":
            switch (settingName) {
            }
            break;
        case "text":
            switch (settingName) {
            case "max-length":
                parseInteger(valueType, settingName, { "none" : 0 });
                return;
            }
            break;
        case "image":
            switch (settingName) {
            case "tooltip":
                parseExpressionOrString(valueType, settingName, { "none" : null });
                return;
            case "max-width":
            case "max-height":
                parseInteger(valueType, settingName, { "none" : -1 });
                return;
            }
            break;
        case "url":
            switch (settingName) {
            case "target":
                parseString(valueType, settingName, { "none" : null });
                return;
            case "external-icon":
                parseURL(valueType, settingName, { "none" : null });
                return;
            }
            break;
        case "item":
            switch (settingName) {
            case "title":
                parseExpression(valueType, settingName, { "default" : null });
                return;
            }
            break;
        case "currency":
            switch (settingName) {
            case "negative-format":
                parseFlags(valueType, settingName,
                    [ "red", "parentheses", "signed" ],
                    { "unsigned" : "signed", "no-parenthesis" : "parentheses", "black" : "red" }
                );
                return;
            case "symbol":
                parseString(valueType, settingName, { "default" : "$", "none" : null });
                return;
            case "symbol-placement":
                parseChoices(valueType, settingName, [ "first", "last", "after-sign" ]);
                return;
            case "decimal-digits":
                parseNonnegativeInteger(valueType, settingName, { "default" : -1 });
                return;
            }
            break;
        case "list":
            switch (settingName) {
            case "separator":
            case "last-separator":
            case "pair-separator":
            case "empty-text":
                parseString(valueType, settingName, {});
                return;
            }
            break;
        }
        throw new Error("Unsupported setting called " + settingName +
            " for value type " + valueType + " found at position " + makePosition());
    };
    var parseSettingList = function(valueType) {

        while (token != null && token.type == Scanner.IDENTIFIER) {
            var settingName = token.value;

            next();

            if (token == null || token.type != Scanner.DELIMITER || token.value != ":") {
                throw new Error("Missing : at position " + makePosition());
            }
            next();

            parseSetting(valueType, settingName);

            if (token == null || token.type != Scanner.DELIMITER || token.value != ";") {
                break;
            } else {
                next();
            }
        }

    }
    var parseRule = function() {
        if (token == null || token.type != Scanner.IDENTIFIER) {
            throw new Error("Missing value type at position " + makePosition());
        }

        var valueType = token.value;
        if (!(valueType in Exhibit.FormatParser._valueTypes)) {
            throw new Error("Unsupported value type " + valueType + " at position " + makePosition());
        }
        next();

        if (token != null && token.type == Scanner.DELIMITER && token.value == "{") {
            next();
            parseSettingList(valueType);

            if (token == null || token.type != Scanner.DELIMITER || token.value != "}") {
                throw new Error("Missing } at position " + makePosition());
            }
            next();
        }
        return valueType;
    };
    var parseRuleList = function() {
        var valueType = "text";
        while (token != null && token.type == Scanner.IDENTIFIER) {
            valueType = parseRule();
        }
        return valueType;
    }

    if (several) {
        return parseRuleList();
    } else {
        return parseRule();
    }
};

/*==================================================
 *  Exhibit.FormatScanner
 *==================================================
 */
Exhibit.FormatScanner = function(text, startIndex) {
    this._text = text + " "; // make it easier to parse
    this._maxIndex = text.length;
    this._index = startIndex;
    this.next();
};

Exhibit.FormatScanner.DELIMITER     = 0;
Exhibit.FormatScanner.NUMBER        = 1;
Exhibit.FormatScanner.STRING        = 2;
Exhibit.FormatScanner.IDENTIFIER    = 3;
Exhibit.FormatScanner.URL           = 4;
Exhibit.FormatScanner.EXPRESSION    = 5;
Exhibit.FormatScanner.COLOR         = 6;

Exhibit.FormatScanner.prototype.token = function() {
    return this._token;
};

Exhibit.FormatScanner.prototype.index = function() {
    return this._index;
};

Exhibit.FormatScanner.prototype.next = function() {
    this._token = null;

    var self = this;
    var skipSpaces = function(x) {
        while (x < self._maxIndex &&
            " \t\r\n".indexOf(self._text.charAt(x)) >= 0) {

            x++;
        }
        return x;
    };
    this._index = skipSpaces(this._index);

    if (this._index < this._maxIndex) {
        var c1 = this._text.charAt(this._index);
        var c2 = this._text.charAt(this._index + 1);

        if ("{}(),:;".indexOf(c1) >= 0) {
            this._token = {
                type:   Exhibit.FormatScanner.DELIMITER,
                value:  c1,
                start:  this._index,
                end:    this._index + 1
            };
            this._index++;
        } else if ("\"'".indexOf(c1) >= 0) { // quoted strings
            var i = this._index + 1;
            while (i < this._maxIndex) {
                if (this._text.charAt(i) == c1 && this._text.charAt(i - 1) != "\\") {
                    break;
                }
                i++;
            }

            if (i < this._maxIndex) {
                this._token = {
                    type:   Exhibit.FormatScanner.STRING,
                    value:  this._text.substring(this._index + 1, i).replace(/\\'/g, "'").replace(/\\"/g, '"'),
                    start:  this._index,
                    end:    i + 1
                };
                this._index = i + 1;
            } else {
                throw new Error("Unterminated string starting at " + this._index);
            }
        } else if (c1 == "#") { // color
            var i = this._index + 1;
            while (i < this._maxIndex && this._isHexDigit(this._text.charAt(i))) {
                i++;
            }

            this._token = {
                type:   Exhibit.FormatScanner.COLOR,
                value:  this._text.substring(this._index, i),
                start:  this._index,
                end:    i
            };
            this._index = i;
        } else if (this._isDigit(c1)) { // number
            var i = this._index;
            while (i < this._maxIndex && this._isDigit(this._text.charAt(i))) {
                i++;
            }

            if (i < this._maxIndex && this._text.charAt(i) == ".") {
                i++;
                while (i < this._maxIndex && this._isDigit(this._text.charAt(i))) {
                    i++;
                }
            }

            this._token = {
                type:   Exhibit.FormatScanner.NUMBER,
                value:  parseFloat(this._text.substring(this._index, i)),
                start:  this._index,
                end:    i
            };
            this._index = i;
        } else { // identifier
            var i = this._index;
            while (i < this._maxIndex) {
                var j = this._text.substr(i).search(/\W/);
                if (j > 0) {
                    i += j;
                } else if ("-".indexOf(this._text.charAt(i)) >= 0) {
                    i++;
                } else {
                    break;
                }
            }

            var identifier = this._text.substring(this._index, i);
            while (true) {
                if (identifier == "url") {
                    var openParen = skipSpaces(i);
                    if (this._text.charAt(openParen) == "(") {
                        var closeParen = this._text.indexOf(")", openParen);
                        if (closeParen > 0) {
                            this._token = {
                                type:   Exhibit.FormatScanner.URL,
                                value:  this._text.substring(openParen + 1, closeParen),
                                start:  this._index,
                                end:    closeParen + 1
                            };
                            this._index = closeParen + 1;
                            break;
                        } else {
                            throw new Error("Missing ) to close url at " + this._index);
                        }
                    }
                } else if (identifier == "expression") {
                    var openParen = skipSpaces(i);
                    if (this._text.charAt(openParen) == "(") {
                        var o = {};
                        var expression = Exhibit.ExpressionParser.parse(this._text, openParen + 1, o);

                        var closeParen = skipSpaces(o.index);
                        if (this._text.charAt(closeParen) == ")") {
                            this._token = {
                                type:   Exhibit.FormatScanner.EXPRESSION,
                                value:  expression,
                                start:  this._index,
                                end:    closeParen + 1
                            };
                            this._index = closeParen + 1;
                            break;
                        } else {
                            throw new Error("Missing ) to close expression at " + o.index);
                        }
                    }
                }

                this._token = {
                    type:   Exhibit.FormatScanner.IDENTIFIER,
                    value:  identifier,
                    start:  this._index,
                    end:    i
                };
                this._index = i;
                break;
            }
        }
    }
};

Exhibit.FormatScanner.prototype._isDigit = function(c) {
    return "0123456789".indexOf(c) >= 0;
};
Exhibit.FormatScanner.prototype._isHexDigit = function(c) {
    return "0123456789abcdefABCDEF".indexOf(c) >= 0;
};
<|endoftext|>


<commit_before>/**
 * Copyright (c) Ajay Sreedhar. All rights reserved.
 *
 * Licensed under the MIT License.
 * Please see LICENSE file located in the project root for more information.
 */

'use strict';

import * as _ from '../../lib/core-toolkit.js';
import {urlQuery, urlOffset} from '../../lib/rest-utils.js';
import {deleteMethodInitiator} from '../helpers/rest-toolkit.js';

/**
 * Provides controller constructor for editing consumer objects.
 *
 * @constructor
 *
 * @param {Object} scope - Injected scope object.
 * @param {RESTClientFactory} restClient - Customised HTTP REST client factory.
 * @param {ViewFrameFactory} viewFrame - Factory for sharing UI details.
 * @param {ToastFactory} toast - Factory for displaying notifications.
 */
export default function ConsumerListController(scope, restClient, viewFrame, toast) {
    scope.consumerList = [];
    scope.consumerNext = {offset: ''};

    /**
     * Retrieves the list of consumers.
     *
     * @param {string|object|null} filters - Filters to the Admin API.
     * @return {boolean} True if request could be made, false otherwise.
     */
    scope.fetchConsumerList = function (filters = null) {
        const request = restClient.get('/consumers' + urlQuery(filters));

        viewFrame.setLoaderSteps(2);
        viewFrame.incrementLoader();

        request.then(({data: response}) => {
            scope.consumerNext.offset = urlOffset(response.next);

            for (let consumer of response.data) {
                let createdAt = new Date(consumer.created_at);

                if (consumer.custom_id === null) {
                    consumer.custom_id = 'Not Provided';
                }

                consumer.created_at = createdAt.toLocaleString();

                scope.consumerList.push(consumer);
            }
        });

        request.catch(() => {
            toast.error('Unable to fetch consumers.');
        });

        request.finally(() => {
            viewFrame.incrementLoader();
        });

        return true;
    };

    /**
     * Deletes the table row entry upon clicking the bin icon.
     *
     * @type {function(Event): boolean}
     */
    scope.deleteTableRow = deleteMethodInitiator(restClient, (err) => {
        if (_.isText(err)) toast.error(err);
        else toast.success('Deleted consumer and credentials.');
    });

    viewFrame.clearBreadcrumbs();
    viewFrame.addBreadcrumb('#!/consumers', 'Consumers');
    viewFrame.setTitle('Consumers');
    viewFrame.addAction('New Consumer', '#!/consumers/__create__');

    scope.fetchConsumerList();
}
<commit_msg>add: Scope cleanup<commit_after>/**
 * Copyright (c) Ajay Sreedhar. All rights reserved.
 *
 * Licensed under the MIT License.
 * Please see LICENSE file located in the project root for more information.
 */

'use strict';

import {isNil, isText} from '../../lib/core-toolkit.js';
import {urlQuery, urlOffset, deleteMethodInitiator} from '../helpers/rest-toolkit.js';
import {epochToDate} from '../helpers/date-lib.js';

/**
 * Provides controller constructor for editing consumer objects.
 *
 * @constructor
 * @param {Object} scope - Injected scope object.
 * @param {RESTClientFactory} restClient - Customised HTTP REST client factory.
 * @param {ViewFrameFactory} viewFrame - Factory for sharing UI details.
 * @param {ToastFactory} toast - Factory for displaying notifications.
 */
export default function ConsumerListController(scope, restClient, viewFrame, toast) {
    scope.consumerList = [];
    scope.consumerNext = {offset: ''};

    /**
     * Retrieves the list of consumers.
     *
     * @param {string|object|null} filters - Filters to the Admin API.
     * @return {boolean} True if request could be made, false otherwise.
     */
    scope.fetchConsumerList = function (filters = null) {
        const request = restClient.get('/consumers' + urlQuery(filters));

        viewFrame.setLoaderSteps(1);

        request.then(({data: response}) => {
            scope.consumerNext.offset = urlOffset(response.next);

            for (let consumer of response.data) {
                if (isNil(consumer.custom_id)) {
                    consumer.custom_id = 'Not Provided';
                }

                consumer.createdAt = epochToDate(consumer.created_at, viewFrame.getFrameConfig('dateFormat'));
                scope.consumerList.push(consumer);
            }
        });

        request.catch(() => {
            toast.error('Unable to fetch consumers.');
        });

        request.finally(() => {
            viewFrame.incrementLoader();
        });

        return true;
    };

    /**
     * Deletes the table row entry upon clicking the bin icon.
     *
     * @type {function(Event): boolean}
     */
    scope.deleteTableRow = deleteMethodInitiator(restClient, (err) => {
        if (isText(err)) toast.error(err);
        else toast.success('Consumer deleted successfully.');
    });

    viewFrame.clearBreadcrumbs();
    viewFrame.addBreadcrumb('#!/consumers', 'Consumers');
    viewFrame.setTitle('Consumers');
    viewFrame.addAction('New Consumer', '#!/consumers/__create__');

    scope.fetchConsumerList();

    scope.$on('$destroy', () => {
        scope.consumerList.length = 0;

        delete scope.consumerList;
        delete scope.fetchConsumerList;
        delete scope.deleteTableRow;
    });
}
<|endoftext|>


<commit_before>/**

  cli

**/

//
//  Dependencies
//

var program    = require("commander"),
    chalk      = require("chalk"),
    data       = require("./data-editor"),
    locals     = require("./locals-editor"),
    middleware = require("./middleware-editor"),
    pipelines  = require("./pipelines-editor"),
    routes     = require("./routes-editor"),
    utils      = require("./cli-utils");

//
//  Exports
//

module.exports = function() {

  var command = process.argv[2];

  // Set start as default argument
  if ("undefined" == typeof process.argv[2]) process.argv[2] = "help";

  // Throw error with pipelines if missing quotes
  if ("pipelines:add" === process.argv[2] && !(/^('.+'|".+")$/.test(process.argv[4]))) {

    // Chalk colors are ignored in console.error. We must use chalk.styles
    var msg = chalk.styles.red.open + "\n   Error! " + chalk.styles.red.close;
    console.error(msg + "Pipelines must be surrounded by quotes to prevent the > operator from creating a file.");
    console.error("          You may want to ensure that no additional file was created.\n");
    return;
  }

  program
    .version("0.7.0");

  program
    .command("start")
    .action(start);

  program
    .command("help")
    .action(showHelp);

  program
    .command("config")
    .action(function() {
      data.list();
      locals.list();
      middleware.list();
      pipelines.list();
      routes.list();
    });

  program
    .command("data")
    .action(data.list);

  program
    .command("data:add <folder> [route]")
    .action(data.add);

  program
    .command("data:remove <folder>")
    .action(data.remove);

  program
    .command("defaults")
    .action(listDefaults);

  program
    .command("locals")
    .action(locals.list);

  program
    .command("locals:add <key> <value>")
    .action(locals.add);

  program
    .command("locals:remove <key>")
    .action(locals.remove);

  program
    .command("middleware")
    .action(middleware.list);

  program
    .command("middleware:add <position> <package>")
    .action(middleware.add);

  program
    .command("middleware:remove <position> <package>")
    .action(middleware.remove);

  program
    .command("pipelines")
    .action(pipelines.list);

  program
    .command("pipelines:add <extension> <pipeline>")
    .action(pipelines.add);

  program
    .command("pipelines:remove <extension>")
    .action(pipelines.remove);

  program
    .command("routes")
    .action(routes.list);

  program
    .command("routes:scripts <route> <folder> <bundleFolders>")
    .action(routes.scripts);

  program
    .command("routes:statics <route> <folder>")
    .action(routes.statics);

  program
    .command("routes:styles <route> <folder>")
    .action(routes.styles);

  program
    .command("routes:views <route> <folder>")
    .action(routes.views);

  program
    .command("routes:remove <route>")
    .action(routes.remove);

  program.parse(process.argv);

};

/**

  Starts the server

**/

function start() {

  var server = require("../server/");

  // Start the server
  console.log(chalk.green.bold("\nStarting server...\n"));
  server({
    liveRefresh: true
  });

  // Start browser sync and proxy
  global.server.init({ proxy : "localhost:" + global.ports.server });

}

/**

  Shows help on commands

**/

function showHelp() {

  var columns = [];

  console.log("");
  console.log("To start the server, run", chalk.magenta("sneakers start"));
  console.log("");
  console.log(chalk.cyan(" Usage:"));
  console.log("");
  console.log("    " + chalk.magenta("sneakers") + chalk.yellow(" <command> [<args>]"));
  console.log("");
  console.log(chalk.cyan(" Commands:"));
  console.log("");

  readHelpFile("main.json").forEach(listCommand);

}

/**

  Helper to read in a file from the help directory

**/

function readHelpFile(filename) {
  return JSON.parse(utils.readFile(require("path").join(__dirname, "help", filename), "utf8"));
}

/**

  Helper to list a command, properly spaced

**/

function listCommand(command, description) {

  var key = command.command;

  // Allow for blank line
  if ("" === key) return console.log("");

  // Pad key
  key = utils.pad(key, 39);

  // Replace <commands> with yellow
  key = key.replace(/\"?<[^>]+>\"?/g, function(x) { return chalk.yellow(x); });
  console.log("    " + key + "  " + command.description);

}

/**

  Lists defaults

**/

function listDefaults() {

  require("./config-editor").loadDefaults();

  console.log("");
  console.log(chalk.magenta(" Note:"), "The following settings are the defaults and may or may not reflect your");
  console.log("       app's configuration. To see your config, use the", chalk.yellow("config"), "command. To");
  console.log("       restore defaults, use the", chalk.yellow("defaults:restore"), "command.");

  data.list();
  locals.list();
  middleware.list();
  pipelines.list();
  routes.list();

}

<commit_msg>cli: adds "defaults:restore" command<commit_after>/**

  cli

**/

//
//  Dependencies
//

var program    = require("commander"),
    chalk      = require("chalk"),
    data       = require("./data-editor"),
    locals     = require("./locals-editor"),
    middleware = require("./middleware-editor"),
    pipelines  = require("./pipelines-editor"),
    routes     = require("./routes-editor"),
    utils      = require("./cli-utils");

//
//  Exports
//

module.exports = function() {

  var command = process.argv[2];

  // Set start as default argument
  if ("undefined" == typeof process.argv[2]) process.argv[2] = "help";

  // Throw error with pipelines if missing quotes
  if ("pipelines:add" === process.argv[2] && !(/^('.+'|".+")$/.test(process.argv[4]))) {

    // Chalk colors are ignored in console.error. We must use chalk.styles
    var msg = chalk.styles.red.open + "\n   Error! " + chalk.styles.red.close;
    console.error(msg + "Pipelines must be surrounded by quotes to prevent the > operator from creating a file.");
    console.error("          You may want to ensure that no additional file was created.\n");
    return;
  }

  program
    .version("0.7.0");

  program
    .command("start")
    .action(start);

  program
    .command("help")
    .action(showHelp);

  program
    .command("config")
    .action(function() {
      data.list();
      locals.list();
      middleware.list();
      pipelines.list();
      routes.list();
    });

  program
    .command("data")
    .action(data.list);

  program
    .command("data:add <folder> [route]")
    .action(data.add);

  program
    .command("data:remove <folder>")
    .action(data.remove);

  program
    .command("defaults")
    .action(listDefaults);

  program
    .command("defaults:restore")
    .action(restoreDefaults);

  program
    .command("locals")
    .action(locals.list);

  program
    .command("locals:add <key> <value>")
    .action(locals.add);

  program
    .command("locals:remove <key>")
    .action(locals.remove);

  program
    .command("middleware")
    .action(middleware.list);

  program
    .command("middleware:add <position> <package>")
    .action(middleware.add);

  program
    .command("middleware:remove <position> <package>")
    .action(middleware.remove);

  program
    .command("pipelines")
    .action(pipelines.list);

  program
    .command("pipelines:add <extension> <pipeline>")
    .action(pipelines.add);

  program
    .command("pipelines:remove <extension>")
    .action(pipelines.remove);

  program
    .command("routes")
    .action(routes.list);

  program
    .command("routes:scripts <route> <folder> <bundleFolders>")
    .action(routes.scripts);

  program
    .command("routes:statics <route> <folder>")
    .action(routes.statics);

  program
    .command("routes:styles <route> <folder>")
    .action(routes.styles);

  program
    .command("routes:views <route> <folder>")
    .action(routes.views);

  program
    .command("routes:remove <route>")
    .action(routes.remove);

  program.parse(process.argv);

};

/**

  Starts the server

**/

function start() {

  var server = require("../server/");

  // Start the server
  console.log(chalk.green.bold("\nStarting server...\n"));
  server({
    liveRefresh: true
  });

  // Start browser sync and proxy
  global.server.init({ proxy : "localhost:" + global.ports.server });

}

/**

  Shows help on commands

**/

function showHelp() {

  var columns = [];

  console.log("");
  console.log("To start the server, run", chalk.magenta("sneakers start"));
  console.log("");
  console.log(chalk.cyan(" Usage:"));
  console.log("");
  console.log("    " + chalk.magenta("sneakers") + chalk.yellow(" <command> [<args>]"));
  console.log("");
  console.log(chalk.cyan(" Commands:"));
  console.log("");

  readHelpFile("main.json").forEach(listCommand);

}

/**

  Helper to read in a file from the help directory

**/

function readHelpFile(filename) {
  return JSON.parse(utils.readFile(require("path").join(__dirname, "help", filename), "utf8"));
}

/**

  Helper to list a command, properly spaced

**/

function listCommand(command, description) {

  var key = command.command;

  // Allow for blank line
  if ("" === key) return console.log("");

  // Pad key
  key = utils.pad(key, 39);

  // Replace <commands> with yellow
  key = key.replace(/\"?<[^>]+>\"?/g, function(x) { return chalk.yellow(x); });
  console.log("    " + key + "  " + command.description);

}

/**

  Lists defaults

**/

function listDefaults() {

  require("./config-editor").loadDefaults();

  console.log("");
  console.log(chalk.magenta(" Note:"), "The following settings are the defaults and may or may not reflect your");
  console.log("       app's configuration. To see your config, use the", chalk.yellow("config"), "command. To");
  console.log("       restore defaults, use the", chalk.yellow("defaults:restore"), "command.");

  data.list();
  locals.list();
  middleware.list();
  pipelines.list();
  routes.list();

}

/**

  Deletes the sneakers.json file and restores defaults.

**/

function restoreDefaults() {

  var str;

  console.log("");
  str  = chalk.red("   Warning!") + " This command will delete your ";
  str += chalk.yellow("sneakers.json") + " file (using default options instead)";
  console.log(str);
  console.log("   To continue type DELETE and press enter.");

  process.stdout.write("   > ");

  process.stdout.on("data", function(res) {
    console.log("");
    res = res.toString().trim();

    if (/^delete$/i.test(res)) {
      console.log("   We've restored defaults by deleting your", chalk.yellow("sneakers.json"), "file");
      require("./config-editor").remove();
    } else
      console.log("   Nothing done. Come back if you need us.");

    console.log("");
    process.exit();
  });

}
<|endoftext|>