<commit_before>/* global $: false */

var core = require('kanso/core'),
    db = require('kanso/db'),
    loader = require('kanso/loader'),
    utils = require('kanso/utils'),
    widgets = require('kanso/widgets'),
    querystring = require('kanso/querystring'),
    _ = require('kanso/underscore')._;

/**
 * Convert an object containing several [ module, callback ] or
 * { module: x, callback: y } items in to an object containing
 * several native javascript functions, by using require.
 *
 * @param actions An object, containing items describing a
 *          function that can be obtained via require().
 */
exports.parseActionCallbacks = function(actions) {
    var rv = {};
    for (var k in actions) {
        var module, callback, action = actions[k];
        if (action instanceof Array) {
            module = action[0];
            callback = action[1];
        } else if (action instanceof Object) {
            module = action.module;
            callback = action.callback;
        } else if (action instanceof Function) {
            rv[k] = action;
            continue;
        } else {
            throw new Error(
                'Action `' + k + '` is of type `' + typeof(action) + '`, ' +
                    "which this function doesn't know how to interpret"
            );
        }
        /* Resolve function description to actual function */
        rv[k] = require(module)[callback];
    }
    return rv;
}

/**
 * Bind all events necessary to manage add, edit, and delete operations
 * on embed and embedList fields. If you wish to override the default
 * actions, you can pass an actions option (i.e. an object of the form
 * { add: x, edit: y, del: z }), where x, y, and z are items as described
 * in parseActionCallbacks.
 */
exports.bindEmbed = function (field, path, value, raw, errors, id) {

    var action_callbacks = exports.parseActionCallbacks(
        (field.widget.actions || {})
    );

    $('#' + id).each(function () {
        exports.initRow(this, action_callbacks, field);
        $('tr', this).each(function () {
            exports.updateRow(this, action_callbacks);
        });
    });
};

/**
 * initRow
 */
exports.initRow = function (row, action_callbacks, field) {
    var action_callbacks = (_.defaults(action_callbacks || {}, {
        add: exports.adminShowModal
    }));
    return exports.createAddBtn(
        row, action_callbacks.add, field
    );
};

/**
 * updateRow
 */
exports.updateRow = function (row, action_callbacks) {
    var val = exports.getRowValue(row);
    var field_td = $(row).parent().parent().parent();

    var action_callbacks = (_.defaults(action_callbacks || {}, {
        edit: exports.adminShowModal
    }));

    if (val) {
        exports.addRowControls(
            row, action_callbacks.edit, action_callbacks.del
        );
    }
    else {
        $(row).remove();
    }
    var row_count = $('tr', field_td).length;
    if (field_td.parent().hasClass('embedded')) {
        if (!row_count) {
            $('.addbtn', field_td).show();
        }
        else {
            $('.addbtn', field_td).hide();
        }
    }
    exports.renumberRows(field_td);
};

/**
 * getRowValue
 */
exports.getRowValue = function (row) {
    var str = $('input:hidden', row).val();
    if (!str) {
        return;
    }
    return JSON.parse(str);
};

/**
 * Generate a document identifier for use in an "add" operation,
 * and place it in the correct hidden input control under div.
 */
exports.generateNewDocumentIdentifier = function(div) {
    db.newUUID(100, function (err, uuid) {
        if (err) {
            throw err;
        }
        $('input[name="_id"]', div).attr({
            value: uuid
        });
    });
}

/**
 * addRowControls
 */
exports.addRowControls = function (row, edit_callback, delete_callback) {
    if (exports.getRowValue(row)) {
        var container = $('td.actions', row).html('');
        var editbtn = $(
            '<input type="button" class="editbtn" value="Edit" />'
        );
        var delbtn  = $(
            '<input type="button" class="delbtn" value="Delete" />'
        );
        editbtn.click(exports.editbtnHandler(edit_callback));
        delbtn.click(exports.delbtnHandler(delete_callback));
        container.append(editbtn, delbtn);
    }
};

/**
 * createAddBtn
 */
exports.createAddBtn = function (field_row, add_callback, field) {
    var addbtn = $(
        '<input type="button" class="addbtn" value="Add" />'
    );
    addbtn.click(
        exports.addbtnHandler(add_callback, field)
    );
    $('.field .addbtn', field_row).remove();
    $('.field', field_row).append(addbtn);
};

/**
 * getModules
 */
exports.getModules = function (/*optional*/req, callback) {
    if (!callback) {
        /* Arity = 1: callback only */
        callback = req;
        req = utils.currentRequest();
    }
    db.getDesignDoc(req.query.app, function (err, ddoc) {
        if (err) {
            throw err;
        }
        var settings = loader.appRequire(ddoc, 'kanso/settings');
        var app = loader.appRequire(ddoc, settings.load);
        var forms = loader.appRequire(ddoc, 'kanso/forms');
        callback(settings, app, forms);
    });
};

/**
 * A showModal wrapper, containing functionality specific to the
 * administrative interface. This function retrieves information
 * from a second application instance, and then calls the usual
 * showModal implementation.
 */
exports.adminShowModal = function (div, field_td, row,
                                   typename, val, rawval, field) {

    exports.getModules(function (settings, app, forms) {
        var type = app.types[typename];
        var form = new forms.Form(type, val);

        return exports.showModal(
            type, form, div, field_td, row,
            typename, val, rawval, field
        );
    });
};

/**
 * Show a modal dialog containing an editable form. Once the
 * editing is completed, call addRow and pass along the JSON-encoded
 * form data.
 */
exports.showModal = function (type, form, div, field_td, row,
                              typename, val, rawval, field) {
    if (rawval) {
        form.validate(rawval);
    }

    div.html('
' + (val ? 'Edit ': 'Add ') + typename + '
');
    div.append('<form>' + form.toHTML() + '</form>');

    var action = (val ? 'Update': 'Add');
    var okbtn = $(
        '<input type="button" value="' + action  + '" />"'
    );

    okbtn.click(function () {
        var qs = $('form', div).serialize().replace(/\+/g, '%20');
        var rawval = querystring.parse(qs);
        form.validate(rawval);

        if (form.isValid()) {
            if (!val) {
                row = exports.addRow(
                    field_td, field, val, rawval, type
                );
            }
            /* Stash JSON-encoded form data in hidden input */
            var jsonval = JSON.stringify(form.values);
            $('input:hidden', row).val(jsonval);
            $('span.value', row).text(form.values._id);
            exports.updateRow(row);
            $.modal.close();
        }
        else {
            /* Repost form showing errors */
            exports.showModal(
                type, form, div, field_td, row,
                    typename, val, rawval, field
            );
        }
    });
    div.append(okbtn);
    div.submit(function (ev) {
        ev.preventDefault();
        okbtn.click();
        return false;
    });

    var cancelbtn = $(
        '<input type="button" value="Cancel" />'
    );
    cancelbtn.click(function () {
        $.modal.close();
    });
    div.append(cancelbtn);

    div.modal();
    utils.resizeModal(div);

    if (!val) {
        exports.generateNewDocumentIdentifier(div);
    }
};

/**
 * renumberRows
 */
exports.renumberRows = function (field_td) {
    var field_row = field_td.parent();
    var name = $('table', field_td).attr('rel');

    if (field_row.hasClass('embeddedlist')) {
        $('tr', field_td).each(function (i) {
            $('input:hidden', this).attr({'name': name + '.' + i});
        });
    }
    else {
        $('input:hidden', field_td).attr({'name': name});
    }
};

/**
 * addRow
 */
exports.addRow = function (field_td, field, val, rawval) {
    var tr = $(
        '
' +
            '' +
                field.widget.toHTML('', val) +
            '	' +
            '	' +
        '
'
    );

    $('tbody', field_td).append(tr);
    return tr;
};

/**
 * getRowType
 */
exports.getRowType = function (row) {
    var field_td = row.parent().parent().parent();
    return field_td.attr('rel');
};

/**
 * addbtnHandler
 */
exports.addbtnHandler = function (add_callback, field) {
    return function (ev) {
        var field_td = $(this).parent();
        var typename = field_td.attr('rel');
        var div = $('
');
        if (add_callback) {
            add_callback(
                div, field_td, null, typename, null, null, field
            );
        }
    };
};

/**
 * editbtnHandler
 */
exports.editbtnHandler = function (edit_callback) {
    return function (ev) {
        var row = $(this).parent().parent();
        var field_td = row.parent().parent().parent();
        var val = exports.getRowValue(row);
        var typename = exports.getRowType(row);
        var div = $('
');
        if (edit_callback) {
            edit_callback(div, field_td, row, typename, val);
        }
    };
};

/**
 * delbtnHandler
 */
exports.delbtnHandler = function (delete_callback) {
    return function (ev) {
        var row = $(this).parent().parent();
        $('input:hidden', row).val('');
        $('span.value', row).html('');
        exports.updateRow(row);
        if (delete_callback) {
            delete_callback(row);
        }
    };
};
<commit_msg>Remove dead code.<commit_after>/* global $: false */

var core = require('kanso/core'),
    db = require('kanso/db'),
    loader = require('kanso/loader'),
    utils = require('kanso/utils'),
    widgets = require('kanso/widgets'),
    querystring = require('kanso/querystring'),
    _ = require('kanso/underscore')._;

/**
 * Convert an object containing several [ module, callback ] or
 * { module: x, callback: y } items in to an object containing
 * several native javascript functions, by using require.
 *
 * @param actions An object, containing items describing a
 *          function that can be obtained via require().
 */
exports.parseActionCallbacks = function(actions) {
    var rv = {};
    for (var k in actions) {
        var module, callback, action = actions[k];
        if (action instanceof Array) {
            module = action[0];
            callback = action[1];
        } else if (action instanceof Object) {
            module = action.module;
            callback = action.callback;
        } else if (action instanceof Function) {
            rv[k] = action;
            continue;
        } else {
            throw new Error(
                'Action `' + k + '` is of type `' + typeof(action) + '`, ' +
                    "which this function doesn't know how to interpret"
            );
        }
        /* Resolve function description to actual function */
        rv[k] = require(module)[callback];
    }
    return rv;
}

/**
 * getModules
 */
exports.getModules = function (/*optional*/req, callback) {
    if (!callback) {
        /* Arity = 1: callback only */
        callback = req;
        req = utils.currentRequest();
    }
    db.getDesignDoc(req.query.app, function (err, ddoc) {
        if (err) {
            throw err;
        }
        var settings = loader.appRequire(ddoc, 'kanso/settings');
        var app = loader.appRequire(ddoc, settings.load);
        var forms = loader.appRequire(ddoc, 'kanso/forms');
        callback(settings, app, forms);
    });
};

/**
 * A showModal wrapper, containing functionality specific to the
 * administrative interface. This function retrieves information
 * from a second application instance, and then calls the usual
 * showModal implementation.
 */
exports.adminShowModal = function (div, field_td, row,
                                   typename, val, rawval, field) {

    exports.getModules(function (settings, app, forms) {
        var type = app.types[typename];
        var form = new forms.Form(type, val);

        return exports.showModal(
            type, form, div, field_td, row,
            typename, val, rawval, field
        );
    });
};

/**
 * Show a modal dialog containing an editable form. Once the
 * editing is completed, call addRow and pass along the JSON-encoded
 * form data.
 */
exports.showModal = function (type, form, div, field_td, row,
                              typename, val, rawval, field) {
    if (rawval) {
        form.validate(rawval);
    }

    div.html('
' + (val ? 'Edit ': 'Add ') + typename + '
');
    div.append('<form>' + form.toHTML() + '</form>');

    var action = (val ? 'Update': 'Add');
    var okbtn = $(
        '<input type="button" value="' + action  + '" />"'
    );

    okbtn.click(function () {
        var qs = $('form', div).serialize().replace(/\+/g, '%20');
        var rawval = querystring.parse(qs);
        form.validate(rawval);

        if (form.isValid()) {
            if (!val) {
                row = exports.addRow(
                    field_td, field, val, rawval, type
                );
            }
            /* Stash JSON-encoded form data in hidden input */
            var jsonval = JSON.stringify(form.values);
            $('input:hidden', row).val(jsonval);
            $('span.value', row).text(form.values._id);
            exports.updateRow(row);
            $.modal.close();
        }
        else {
            /* Repost form showing errors */
            exports.showModal(
                type, form, div, field_td, row,
                    typename, val, rawval, field
            );
        }
    });
    div.append(okbtn);
    div.submit(function (ev) {
        ev.preventDefault();
        okbtn.click();
        return false;
    });

    var cancelbtn = $(
        '<input type="button" value="Cancel" />'
    );
    cancelbtn.click(function () {
        $.modal.close();
    });
    div.append(cancelbtn);

    div.modal();
    utils.resizeModal(div);

    if (!val) {
        exports.generateNewDocumentIdentifier(div);
    }
};

<|endoftext|>


<commit_before>const os = require('os');
const fs = require('fs');
const checker = require('spellchecker');
const { clipboard, remote, webFrame, shell } = require('electron');
const { MenuItem, dialog } = remote;
const i18n = require('../../i18n/index');

const webContents = remote.getCurrentWebContents();
let menu = new remote.Menu();

const path = remote.require('path');
const isWindows = ['win32', 'win64'].indexOf(os.platform()) !== -1;

class SpellCheck {

	get userLanguage() {
		const lang = localStorage.getItem('userLanguage');
		return lang ? lang.replace('-', '_') : null;
	}

	get dictionaries() {
		const dictionaries = localStorage.getItem('spellcheckerDictionaries');
		const result = JSON.parse(dictionaries || '[]');
		return Array.isArray(result) ? result : [];
	}

	constructor() {
		this.enabledDictionaries = [];
		this.contractions = this.getContractions();
		this.loadAvailableDictionaries();
		this.setEnabledDictionaries();

		this.languagesMenu = {
			label: i18n.__('Spelling_languages'),
			submenu: this.availableDictionaries.map((dictionary) => {
				const menu = {
					label: dictionary,
					type: 'checkbox',
					checked: this.enabledDictionaries.includes(dictionary),
					click: (menuItem) => {
						menu.checked = menuItem.checked;
						// If not using os dictionary then limit to only 1 language
						if (!this.multiLanguage && this.languagesMenu.submenu) {
							this.languagesMenu.submenu.forEach((m) => {
								if (m.label !== menuItem.label) {
									m.checked = false;
								}
							});
						}
						if (menuItem.checked) {
							this.setEnabled(dictionary);
						} else {
							this.disable(dictionary);
						}
						this.saveEnabledDictionaries();
					},
				};
				return menu;
			}),
		};

		this.browseForLanguageMenu = new MenuItem({
			label: i18n.__('Browse_for_language'),
			click: () => {
				dialog.showOpenDialog({
					title: i18n.__('Open_Language_Dictionary'),
					defaultPath: this.dictionariesPath,
					filters: { name: 'Dictionaries', extensions: ['aff', 'dic'] },
					properties: ['openFile', 'multiSelections'],
				},
				(filePaths) => { this.installDictionariesFromPaths(filePaths); }
				);
			},
		});
	}

	/**
     * Set enabled dictionaries on load
     * Either sets enabled dictionaries to saved preferences, or enables the first
     * dictionary that is valid based on system (defaults to en_US)
     */
	setEnabledDictionaries() {
		const { dictionaries } = this;
		if (dictionaries) {
			// Dictionary disabled
			if (dictionaries.length === 0) {
				return;
			}
			if (this.setEnabled(dictionaries)) {
				return;
			}
		}

		if (this.userLanguage) {
			if (this.setEnabled(this.userLanguage)) {
				return;
			}
			if (this.userLanguage.includes('_') && this.setEnabled(this.userLanguage.split('_')[0])) {
				return;
			}
		}

		const navigatorLanguage = navigator.language.replace('-', '_');
		if (this.setEnabled(navigatorLanguage)) {
			return;
		}

		if (navigatorLanguage.includes('_') && this.setEnabled(navigatorLanguage.split('_')[0])) {
			return;
		}

		if (this.setEnabled('en_US')) {
			return;
		}

		if (!this.setEnabled('en')) {
			console.info('Unable to set a language for the spell checker - Spell checker is disabled');
		}

	}

	loadAvailableDictionaries() {
		this.availableDictionaries = checker.getAvailableDictionaries().sort();
		if (this.availableDictionaries.length === 0) {
			this.multiLanguage = false;
			// Dictionaries path is correct for build
			this.dictionariesPath = path.join(
				remote.app.getAppPath(),
				process.mainModule.filename.indexOf('app.asar') !== -1 ? '..' : '.',
				'dictionaries'
			);
			this.getDictionariesFromInstallDirectory();
		} else {
			this.multiLanguage = !isWindows;
			this.availableDictionaries = this.availableDictionaries.map((dict) => dict.replace('-', '_'));
		}
	}

	/**
     * Installs all of the dictionaries specified in filePaths
     * Copies dicts into our dictionary path and adds them to availableDictionaries
     */
	installDictionariesFromPaths(dictionaryPaths) {
		for (const dictionaryPath of dictionaryPaths) {
			const dictionaryFileName = dictionaryPath.split(path.sep).pop();
			const dictionaryName = dictionaryFileName.slice(0, -4);
			const newDictionaryPath = path.join(this.dictionariesPath, dictionaryFileName);

			this.copyDictionaryToInstallDirectory(dictionaryName, dictionaryPath, newDictionaryPath);
		}
	}

	copyDictionaryToInstallDirectory(dictionaryName, oldPath, newPath) {
		fs.createReadStream(oldPath).pipe(fs.createWriteStream(newPath)
			.on('error', (errorMessage) => {
				dialog.showErrorBox(i18n.__('Error'), `${ i18n.__('Error copying dictionary file') }: ${ dictionaryName }`);
				console.error(errorMessage);
			})
			.on('finish', () => {
				if (!this.availableDictionaries.includes(dictionaryName)) {
					this.availableDictionaries.push(dictionaryName);
				}
			}));
	}

	getDictionariesFromInstallDirectory() {
		if (this.dictionariesPath) {
			const fileNames = fs.readdirSync(this.dictionariesPath);
			for (const fileName of fileNames) {
				const dictionaryExtension = fileName.slice(-3);
				const dictionaryName = fileName.slice(0, -4);
				if (!this.availableDictionaries.includes(dictionaryName)
                    && (dictionaryExtension === 'aff' || dictionaryExtension === 'dic')) {
					this.availableDictionaries.push(dictionaryName);
				}
			}
		}
	}

	setEnabled(dictionaries) {
		dictionaries = [].concat(dictionaries);
		let result = false;
		for (let i = 0; i < dictionaries.length; i++) {
			if (this.availableDictionaries.includes(dictionaries[i])) {
				result = true;
				this.enabledDictionaries.push(dictionaries[i]);
				// If using Hunspell or Windows then only allow 1 language for performance reasons
				if (!this.multiLanguage) {
					this.enabledDictionaries = [dictionaries[i]];
					checker.setDictionary(dictionaries[i], this.dictionariesPath);
					return true;
				}
			}
		}
		return result;
	}

	disable(dictionary) {
		const pos = this.enabledDictionaries.indexOf(dictionary);
		if (pos !== -1) {
			this.enabledDictionaries.splice(pos, 1);
		}
	}

	getContractions() {
		const contractions = [
			"ain't", "aren't", "can't", "could've", "couldn't", "couldn't've", "didn't", "doesn't", "don't", "hadn't",
			"hadn't've", "hasn't", "haven't", "he'd", "he'd've", "he'll", "he's", "how'd", "how'll", "how's", "I'd",
			"I'd've", "I'll", "I'm", "I've", "isn't", "it'd", "it'd've", "it'll", "it's", "let's", "ma'am", "mightn't",
			"mightn't've", "might've", "mustn't", "must've", "needn't", "not've", "o'clock", "shan't", "she'd", "she'd've",
			"she'll", "she's", "should've", "shouldn't", "shouldn't've", "that'll", "that's", "there'd", "there'd've",
			"there're", "there's", "they'd", "they'd've", "they'll", "they're", "they've", "wasn't", "we'd", "we'd've",
			"we'll", "we're", "we've", "weren't", "what'll", "what're", "what's", "what've", "when's", "where'd",
			"where's", "where've", "who'd", "who'll", "who're", "who's", "who've", "why'll", "why're", "why's", "won't",
			"would've", "wouldn't", "wouldn't've", "y'all", "y'all'd've", "you'd", "you'd've", "you'll", "you're", "you've",
		];

		const contractionMap = contractions.reduce((acc, word) => {
			acc[word.replace(/'.*/, '')] = true;
			return acc;
		}, {});

		return contractionMap;
	}

	enable() {
		webFrame.setSpellCheckProvider('', false, {
			spellCheck: (text) => this.isCorrect(text),
		});

		this.setupContextMenuListener();
	}

	getMenu() {
		return [
			{
				label: i18n.__('&Undo'),
				role: 'undo',
			},
			{
				label: i18n.__('&Redo'),
				role: 'redo',
			},
			{
				type: 'separator',
			},
			{
				label: i18n.__('Cu&t'),
				role: 'cut',
				accelerator: 'CommandOrControl+X',
			},
			{
				label: i18n.__('&Copy'),
				role: 'copy',
				accelerator: 'CommandOrControl+C',
			},
			{
				label: i18n.__('&Paste'),
				role: 'paste',
				accelerator: 'CommandOrControl+V',
			},
			{
				label: i18n.__('Select &all'),
				role: 'selectall',
				accelerator: 'CommandOrControl+A',
			},
		];
	}

	saveEnabledDictionaries() {
		localStorage.setItem('spellcheckerDictionaries', JSON.stringify(this.enabledDictionaries));
	}

	isCorrect(text) {
		if (!this.enabledDictionaries.length || this.contractions[text.toLocaleLowerCase()]) {
			return true;
		}

		if (this.multiLanguage) {
			for (let i = 0; i < this.enabledDictionaries.length; i++) {
				checker.setDictionary(this.enabledDictionaries[i]);
				if (!checker.isMisspelled(text)) {
					return true;
				}
			}
		} else {
			return !checker.isMisspelled(text);
		}
		return false;
	}

	getCorrections(text) {
		if (!this.multiLanguage) {
			return checker.getCorrectionsForMisspelling(text);
		}

		const allCorrections = this.enabledDictionaries.map((dictionary) => {
			checker.setDictionary(dictionary);
			return checker.getCorrectionsForMisspelling(text);
		}).filter((c) => c.length > 0);

		const length = Math.max(...allCorrections.map((a) => a.length));

		// Get the best suggestions of each language first
		const corrections = [];
		for (let i = 0; i < length; i++) {
			corrections.push(...allCorrections.map((c) => c[i]).filter((c) => c));
		}

		// Remove duplicates
		return [...new Set(corrections)];
	}

	setupContextMenuListener() {
		window.addEventListener('contextmenu', (event) => {
			event.preventDefault();

			const template = this.getMenu();

			if (this.languagesMenu && this.browseForLanguageMenu) {
				template.unshift({ type: 'separator' });
				if (this.dictionariesPath) {
					template.unshift(this.browseForLanguageMenu);
				}
				template.unshift(this.languagesMenu);
			}

			setTimeout(() => {
				if (event.target.nodeName === 'A') {
					const targetLink = event.target.href;

					template.unshift({
						label: i18n.__('Open_Link'),
						click: () => {
							shell.openExternal(targetLink);
						},
					});
					template.unshift({
						label: i18n.__('Copy_Link'),
						click: () => {
							clipboard.writeText(targetLink);
						},
					});
				}

				if (['TEXTAREA', 'INPUT'].indexOf(event.target.nodeName) > -1) {
					const text = window.getSelection().toString().trim();
					if (text !== '' && !this.isCorrect(text)) {
						const options = this.getCorrections(text);
						const maxItems = Math.min(options.length, 6);

						if (maxItems > 0) {
							const suggestions = [];
							const onClick = function(menuItem) {
								webContents.replaceMisspelling(menuItem.label);
							};

							for (let i = 0; i < options.length; i++) {
								const item = options[i];
								suggestions.push({ label: item, click: onClick });
							}

							template.unshift({ type: 'separator' });

							if (suggestions.length > maxItems) {
								const moreSuggestions = {
									label: i18n.__('More_spelling_suggestions'),
									submenu: suggestions.slice(maxItems),
								};
								template.unshift(moreSuggestions);
							}

							template.unshift.apply(template, suggestions.slice(0, maxItems));
						} else {
							template.unshift({ label: i18n.__('No_suggestions'), enabled: false });
						}
					}
				}

				menu = remote.Menu.buildFromTemplate(template);
				menu.popup(remote.getCurrentWindow(), undefined, undefined, 5);
			}, 0);
		}, false);
	}
}

module.exports = SpellCheck;
<commit_msg>Refactor spell checker code<commit_after>const fs = require('fs');
const path = require('path');
const checker = require('spellchecker');
const { clipboard, remote, shell, webFrame } = require('electron');
const i18n = require('../../i18n/index');

const { app, dialog, getCurrentWebContents, getCurrentWindow, Menu, MenuItem } = remote;

const webContents = getCurrentWebContents();
let menu = new Menu();

class SpellCheck {

	constructor() {
		this.enabledDictionaries = [];

		this.contractions = [
			"ain't", "aren't", "can't", "could've", "couldn't", "couldn't've", "didn't", "doesn't", "don't", "hadn't",
			"hadn't've", "hasn't", "haven't", "he'd", "he'd've", "he'll", "he's", "how'd", "how'll", "how's", "I'd",
			"I'd've", "I'll", "I'm", "I've", "isn't", "it'd", "it'd've", "it'll", "it's", "let's", "ma'am", "mightn't",
			"mightn't've", "might've", "mustn't", "must've", "needn't", "not've", "o'clock", "shan't", "she'd", "she'd've",
			"she'll", "she's", "should've", "shouldn't", "shouldn't've", "that'll", "that's", "there'd", "there'd've",
			"there're", "there's", "they'd", "they'd've", "they'll", "they're", "they've", "wasn't", "we'd", "we'd've",
			"we'll", "we're", "we've", "weren't", "what'll", "what're", "what's", "what've", "when's", "where'd",
			"where's", "where've", "who'd", "who'll", "who're", "who's", "who've", "why'll", "why're", "why's", "won't",
			"would've", "wouldn't", "wouldn't've", "y'all", "y'all'd've", "you'd", "you'd've", "you'll", "you're", "you've",
		].reduce((contractionMap, word) => {
			contractionMap[word.replace(/'.*/, '')] = true;
			return contractionMap;
		}, {});

		this.loadAvailableDictionaries();
		this.setEnabledDictionaries();

		this.languagesMenu = {
			label: i18n.__('Spelling_languages'),
			submenu: this.availableDictionaries.map((dictionary) => {
				const menu = {
					label: dictionary,
					type: 'checkbox',
					checked: this.enabledDictionaries.includes(dictionary),
					click: (menuItem) => {
						menu.checked = menuItem.checked;
						// If not using os dictionary then limit to only 1 language
						if (!this.multiLanguage && this.languagesMenu.submenu) {
							this.languagesMenu.submenu.forEach((m) => {
								if (m.label !== menuItem.label) {
									m.checked = false;
								}
							});
						}
						if (menuItem.checked) {
							this.setEnabled(dictionary);
						} else {
							this.disable(dictionary);
						}
						this.saveEnabledDictionaries();
					},
				};
				return menu;
			}),
		};

		this.browseForLanguageMenu = new MenuItem({
			label: i18n.__('Browse_for_language'),
			click: () => {
				dialog.showOpenDialog({
					title: i18n.__('Open_Language_Dictionary'),
					defaultPath: this.dictionariesPath,
					filters: { name: 'Dictionaries', extensions: ['aff', 'dic'] },
					properties: ['openFile', 'multiSelections'],
				},
				(filePaths) => { this.installDictionariesFromPaths(filePaths); }
				);
			},
		});
	}

	get userLanguage() {
		const lang = localStorage.getItem('userLanguage');
		return lang ? lang.replace('-', '_') : null;
	}

	get dictionaries() {
		const dictionaries = localStorage.getItem('spellcheckerDictionaries');
		const result = JSON.parse(dictionaries || '[]');
		return Array.isArray(result) ? result : [];
	}

	/**
     * Set enabled dictionaries on load
     * Either sets enabled dictionaries to saved preferences, or enables the first
     * dictionary that is valid based on system (defaults to en_US)
     */
	setEnabledDictionaries() {
		const { dictionaries } = this;
		if (dictionaries) {
			// Dictionary disabled
			if (dictionaries.length === 0) {
				return;
			}
			if (this.setEnabled(dictionaries)) {
				return;
			}
		}

		if (this.userLanguage) {
			if (this.setEnabled(this.userLanguage)) {
				return;
			}
			if (this.userLanguage.includes('_') && this.setEnabled(this.userLanguage.split('_')[0])) {
				return;
			}
		}

		const navigatorLanguage = navigator.language.replace('-', '_');
		if (this.setEnabled(navigatorLanguage)) {
			return;
		}

		if (navigatorLanguage.includes('_') && this.setEnabled(navigatorLanguage.split('_')[0])) {
			return;
		}

		if (this.setEnabled('en_US')) {
			return;
		}

		if (!this.setEnabled('en')) {
			console.info('Unable to set a language for the spell checker - Spell checker is disabled');
		}

	}

	loadAvailableDictionaries() {
		this.availableDictionaries = checker.getAvailableDictionaries().sort();
		if (this.availableDictionaries.length === 0) {
			this.multiLanguage = false;
			// Dictionaries path is correct for build
			this.dictionariesPath = path.join(
				app.getAppPath(),
				process.mainModule.filename.indexOf('app.asar') !== -1 ? '..' : '.',
				'dictionaries'
			);
			this.getDictionariesFromInstallDirectory();
		} else {
			this.multiLanguage = process.platform !== 'win32';
			this.availableDictionaries = this.availableDictionaries.map((dict) => dict.replace('-', '_'));
		}
	}

	/**
     * Installs all of the dictionaries specified in filePaths
     * Copies dicts into our dictionary path and adds them to availableDictionaries
     */
	installDictionariesFromPaths(dictionaryPaths) {
		for (const dictionaryPath of dictionaryPaths) {
			const dictionaryFileName = dictionaryPath.split(path.sep).pop();
			const dictionaryName = dictionaryFileName.slice(0, -4);
			const newDictionaryPath = path.join(this.dictionariesPath, dictionaryFileName);

			this.copyDictionaryToInstallDirectory(dictionaryName, dictionaryPath, newDictionaryPath);
		}
	}

	copyDictionaryToInstallDirectory(dictionaryName, oldPath, newPath) {
		fs.createReadStream(oldPath).pipe(fs.createWriteStream(newPath)
			.on('error', (errorMessage) => {
				dialog.showErrorBox(i18n.__('Error'), `${ i18n.__('Error copying dictionary file') }: ${ dictionaryName }`);
				console.error(errorMessage);
			})
			.on('finish', () => {
				if (!this.availableDictionaries.includes(dictionaryName)) {
					this.availableDictionaries.push(dictionaryName);
				}
			}));
	}

	getDictionariesFromInstallDirectory() {
		if (this.dictionariesPath) {
			const fileNames = fs.readdirSync(this.dictionariesPath);
			for (const fileName of fileNames) {
				const dictionaryExtension = fileName.slice(-3);
				const dictionaryName = fileName.slice(0, -4);
				if (!this.availableDictionaries.includes(dictionaryName)
                    && (dictionaryExtension === 'aff' || dictionaryExtension === 'dic')) {
					this.availableDictionaries.push(dictionaryName);
				}
			}
		}
	}

	setEnabled(dictionaries) {
		dictionaries = [].concat(dictionaries);
		let result = false;
		for (let i = 0; i < dictionaries.length; i++) {
			if (this.availableDictionaries.includes(dictionaries[i])) {
				result = true;
				this.enabledDictionaries.push(dictionaries[i]);
				// If using Hunspell or Windows then only allow 1 language for performance reasons
				if (!this.multiLanguage) {
					this.enabledDictionaries = [dictionaries[i]];
					checker.setDictionary(dictionaries[i], this.dictionariesPath);
					return true;
				}
			}
		}
		return result;
	}

	disable(dictionary) {
		const pos = this.enabledDictionaries.indexOf(dictionary);
		if (pos !== -1) {
			this.enabledDictionaries.splice(pos, 1);
		}
	}

	enable() {
		webFrame.setSpellCheckProvider('', false, {
			spellCheck: (text) => this.isCorrect(text),
		});

		this.setupContextMenuListener();
	}

	createMenuTemplate() {
		return [
			{
				label: i18n.__('&Undo'),
				role: 'undo',
				accelerator: 'CommandOrControl+Z',
			},
			{
				label: i18n.__('&Redo'),
				role: 'redo',
				accelerator: process.platform === 'win32' ? 'Control+Y' : 'CommandOrControl+Shift+Z',
			},
			{
				type: 'separator',
			},
			{
				label: i18n.__('Cu&t'),
				role: 'cut',
				accelerator: 'CommandOrControl+X',
			},
			{
				label: i18n.__('&Copy'),
				role: 'copy',
				accelerator: 'CommandOrControl+C',
			},
			{
				label: i18n.__('&Paste'),
				role: 'paste',
				accelerator: 'CommandOrControl+V',
			},
			{
				label: i18n.__('Select &all'),
				role: 'selectall',
				accelerator: 'CommandOrControl+A',
			},
		];
	}

	saveEnabledDictionaries() {
		localStorage.setItem('spellcheckerDictionaries', JSON.stringify(this.enabledDictionaries));
	}

	isCorrect(text) {
		if (!this.enabledDictionaries.length || this.contractions[text.toLocaleLowerCase()]) {
			return true;
		}

		if (this.multiLanguage) {
			for (let i = 0; i < this.enabledDictionaries.length; i++) {
				checker.setDictionary(this.enabledDictionaries[i]);
				if (!checker.isMisspelled(text)) {
					return true;
				}
			}
		} else {
			return !checker.isMisspelled(text);
		}
		return false;
	}

	getCorrections(text) {
		if (!this.multiLanguage) {
			return checker.getCorrectionsForMisspelling(text);
		}

		const allCorrections = this.enabledDictionaries.map((dictionary) => {
			checker.setDictionary(dictionary);
			return checker.getCorrectionsForMisspelling(text);
		}).filter((c) => c.length > 0);

		const length = Math.max(...allCorrections.map((a) => a.length));

		// Get the best suggestions of each language first
		const corrections = [];
		for (let i = 0; i < length; i++) {
			corrections.push(...allCorrections.map((c) => c[i]).filter((c) => c));
		}

		// Remove duplicates
		return [...new Set(corrections)];
	}

	setupContextMenuListener() {
		window.addEventListener('contextmenu', (event) => {
			event.preventDefault();

			const template = this.createMenuTemplate();

			if (this.languagesMenu && this.browseForLanguageMenu) {
				template.unshift({ type: 'separator' });
				if (this.dictionariesPath) {
					template.unshift(this.browseForLanguageMenu);
				}
				template.unshift(this.languagesMenu);
			}

			setTimeout(() => {
				if (event.target.nodeName === 'A') {
					const targetLink = event.target.href;

					template.unshift({
						label: i18n.__('Open_Link'),
						click: () => {
							shell.openExternal(targetLink);
						},
					});
					template.unshift({
						label: i18n.__('Copy_Link'),
						click: () => {
							clipboard.writeText(targetLink);
						},
					});
				}

				if (['TEXTAREA', 'INPUT'].indexOf(event.target.nodeName) > -1) {
					const text = window.getSelection().toString().trim();
					if (text !== '' && !this.isCorrect(text)) {
						const options = this.getCorrections(text);
						const maxItems = Math.min(options.length, 6);

						if (maxItems > 0) {
							const suggestions = [];
							const onClick = function(menuItem) {
								webContents.replaceMisspelling(menuItem.label);
							};

							for (let i = 0; i < options.length; i++) {
								const item = options[i];
								suggestions.push({ label: item, click: onClick });
							}

							template.unshift({ type: 'separator' });

							if (suggestions.length > maxItems) {
								const moreSuggestions = {
									label: i18n.__('More_spelling_suggestions'),
									submenu: suggestions.slice(maxItems),
								};
								template.unshift(moreSuggestions);
							}

							template.unshift.apply(template, suggestions.slice(0, maxItems));
						} else {
							template.unshift({ label: i18n.__('No_suggestions'), enabled: false });
						}
					}
				}

				menu = Menu.buildFromTemplate(template);
				menu.popup(getCurrentWindow(), undefined, undefined, 5);
			}, 0);
		}, false);
	}
}

module.exports = SpellCheck;
<|endoftext|>


<commit_before>import ReactiveElement from "../core/ReactiveElement.js";
import * as template from "../core/template.js";
import * as calendar from "./calendar.js";
import CalendarDay from "./CalendarDay.js";
import CalendarDayNamesHeader from "./CalendarDayNamesHeader.js";
import CalendarDays from "./CalendarDays.js";
import CalendarElementMixin from "./CalendarElementMixin.js";
import CalendarMonthYearHeader from "./CalendarMonthYearHeader.js";
import * as internal from "./internal.js";

const Base = CalendarElementMixin(ReactiveElement);

/**
 * A single calendar month, optimized for a given locale
 *
 * [A default representation for the current month in browser's default locale](/demos/calendarMonth.html)
 *
 * Given a reference `date` property, this component will show a calendar
 * representation of that month. To the extent possible, this representation is
 * sensitive to a specified locale: the names of the months and days of the week
 * will be in the appropriate language, and the day(s) associated with the
 * weekend in that locale will also be indicated.
 *
 * @inherits ReactiveElement
 * @mixes CalendarElementMixin
 * @part {CalendarDay} day - any of the day elements in the month grid
 * @part {CalendarDayNamesHeader} day-names-header - the column header showing the names of the days
 * @part {CalendarDays} month-days - the grid of days for the month
 * @part {CalendarMonthYearHeader} month-year-header - the calendar header showing the month and/or year
 * @part day-name - any of the names for the days of the week
 */
class CalendarMonth extends Base {
  /**
   * Returns the day element corresponding to the given date, or null if the
   * date falls outside the range currently covered by this calendar.
   *
   * @param {Date} date - the date to search for
   * @returns {Element|null}
   */
  dayElementForDate(date) {
    /** @type {any} */
    const monthDays = this[internal.ids].monthDays;
    return (
      monthDays &&
      "dayElementForDate" in monthDays &&
      monthDays.dayElementForDate(date)
    );
  }

  /**
   * The class or tag used to create the header showing the
   * day names.
   *
   * @type {PartDescriptor}
   * @default CalendarDayNamesHeader
   */
  get dayNamesHeaderPartType() {
    return this[internal.state].dayNamesHeaderPartType;
  }
  set dayNamesHeaderPartType(dayNamesHeaderPartType) {
    this[internal.setState]({ dayNamesHeaderPartType });
  }

  /**
   * The class or tag used to create the `day` parts – the set of
   * days shown in the calendar grid.
   *
   * @type {PartDescriptor}
   * @default CalendarDay
   */
  get dayPartType() {
    return this[internal.state].dayPartType;
  }
  set dayPartType(dayPartType) {
    this[internal.setState]({ dayPartType });
  }

  /**
   * Returns the day elements contained by this calendar. Note that this may
   * include days from the previous/next month that fall in the same week as
   * the first/last day of the present month.
   *
   * @type {Element[]}
   */
  get days() {
    return this[internal.shadowRoot]
      ? /** @type {any} */ (this[internal.ids].monthDays).days
      : [];
  }

  /**
   * The format used to render the day names in the week days header.
   *
   * The allowable formats are the same as the `weekday` formats in
   * [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat).
   *
   * @type {('long'|'narrow'|'short')}
   * @default 'short'
   */
  get daysOfWeekFormat() {
    return this[internal.state].daysOfWeekFormat;
  }
  set daysOfWeekFormat(daysOfWeekFormat) {
    this[internal.setState]({ daysOfWeekFormat });
  }

  get [internal.defaultState]() {
    return Object.assign(super[internal.defaultState], {
      date: calendar.today(),
      dayNamesHeaderPartType: CalendarDayNamesHeader,
      dayPartType: CalendarDay,
      daysOfWeekFormat: "short",
      monthDaysPartType: CalendarDays,
      monthFormat: "long",
      monthYearHeaderPartType: CalendarMonthYearHeader,
      showCompleteWeeks: false,
      showSelectedDay: false,
      yearFormat: "numeric",
    });
  }

  /**
   * The format used to render the month name.
   *
   * The allowable formats are the same as the `month` formats in
   * [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat).
   *
   * @type {('numeric'|'2-digit'|'long'|'short'|'narrow')}
   * @default 'long'
   */
  get monthFormat() {
    return this[internal.state].monthFormat;
  }
  set monthFormat(monthFormat) {
    this[internal.setState]({ monthFormat });
  }

  /**
   * The class or tag used to create the grid of days.
   *
   * @type {PartDescriptor}
   * @default CalendarDays
   */
  get monthDaysPartType() {
    return this[internal.state].monthDaysPartType;
  }
  set monthDaysPartType(monthDaysPartType) {
    this[internal.setState]({ monthDaysPartType });
  }

  /**
   * The class or tag used to create the header showing the
   * month and year.
   *
   * @type {PartDescriptor}
   * @default CalendarMonthYearHeader
   */
  get monthYearHeaderPartType() {
    return this[internal.state].monthYearHeaderPartType;
  }
  set monthYearHeaderPartType(monthYearHeaderPartType) {
    this[internal.setState]({ monthYearHeaderPartType });
  }

  [internal.render](/** @type {ChangedFlags} */ changed) {
    super[internal.render](changed);
    renderParts(this[internal.shadowRoot], this[internal.state], changed);

    if (changed.dayPartType || changed.monthDaysPartType) {
      /** @type {any} */ (this[internal.ids].monthDays).dayPartType = this[
        internal.state
      ].dayPartType;
    }

    if (
      changed.locale ||
      changed.monthDaysPartType ||
      changed.monthYearHeaderPartType ||
      changed.dayNamesHeaderPartType
    ) {
      const locale = this[internal.state].locale;
      /** @type {any} */ (this[internal.ids].monthDays).locale = locale;
      /** @type {any} */ (this[internal.ids].monthYearHeader).locale = locale;
      /** @type {any} */ (this[internal.ids].dayNamesHeader).locale = locale;
    }

    if (changed.date || changed.monthDaysPartType) {
      const { date } = this[internal.state];
      if (date) {
        const startDate = calendar.firstDateOfMonth(date);
        const endDate = calendar.lastDateOfMonth(date);
        const dayCount = endDate.getDate();
        Object.assign(this[internal.ids].monthDays, {
          date,
          dayCount,
          startDate,
        });
        /** @type {any} */ (this[internal.ids]
          .monthYearHeader).date = calendar.firstDateOfMonth(date);
      }
    }

    if (changed.daysOfWeekFormat || changed.dayNamesHeaderPartType) {
      const { daysOfWeekFormat } = this[internal.state];
      /** @type {any} */ (this[internal.ids]
        .dayNamesHeader).format = daysOfWeekFormat;
    }

    if (changed.showCompleteWeeks || changed.monthDaysPartType) {
      const { showCompleteWeeks } = this[internal.state];
      /** @type {any} */ (this[internal.ids]
        .monthDays).showCompleteWeeks = showCompleteWeeks;
    }

    if (changed.showSelectedDay || changed.monthDaysPartType) {
      const { showSelectedDay } = this[internal.state];
      /** @type {any} */ (this[internal.ids]
        .monthDays).showSelectedDay = showSelectedDay;
    }

    if (changed.monthFormat || changed.monthYearHeaderPartType) {
      const { monthFormat } = this[internal.state];
      /** @type {any} */ (this[internal.ids]
        .monthYearHeader).monthFormat = monthFormat;
    }

    if (changed.yearFormat || changed.monthYearHeaderPartType) {
      const { yearFormat } = this[internal.state];
      /** @type {any} */ (this[internal.ids]
        .monthYearHeader).yearFormat = yearFormat;
    }
  }

  get showCompleteWeeks() {
    return this[internal.state].showCompleteWeeks;
  }
  set showCompleteWeeks(showCompleteWeeks) {
    this[internal.setState]({ showCompleteWeeks });
  }

  get showSelectedDay() {
    return this[internal.state].showSelectedDay;
  }
  set showSelectedDay(showSelectedDay) {
    this[internal.setState]({ showSelectedDay });
  }

  get [internal.template]() {
    const result = template.html`

    `;
    renderParts(result.content, this[internal.state]);
    return result;
  }

  /**
   * The format used to render the year.
   *
   * The allowable formats are the same as the `year` formats in
   * [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat).
   *
   * @type {('numeric'|'2-digit')}
   * @default 'numeric'
   */
  get yearFormat() {
    return this[internal.state].yearFormat;
  }
  set yearFormat(yearFormat) {
    this[internal.setState]({ yearFormat });
  }
}

/**
 * Render parts for the template or an instance.
 *
 * @private
 * @param {DocumentFragment} root
 * @param {PlainObject} state
 * @param {ChangedFlags} [changed]
 */
function renderParts(root, state, changed) {
  if (!changed || changed.dayNamesHeaderPartType) {
    const { dayNamesHeaderPartType } = state;
    const dayNamesHeader = root.getElementById("dayNamesHeader");
    if (dayNamesHeader) {
      template.transmute(dayNamesHeader, dayNamesHeaderPartType);
    }
  }
  if (!changed || changed.monthYearHeaderPartType) {
    const { monthYearHeaderPartType } = state;
    const monthYearHeader = root.getElementById("monthYearHeader");
    if (monthYearHeader) {
      template.transmute(monthYearHeader, monthYearHeaderPartType);
    }
  }
  if (!changed || changed.monthDaysPartType) {
    const { monthDaysPartType } = state;
    const monthDays = root.getElementById("monthDays");
    if (monthDays) {
      template.transmute(monthDays, monthDaysPartType);
    }
  }
}

export default CalendarMonth;
<commit_msg>Fix calendar styling.<commit_after>import ReactiveElement from "../core/ReactiveElement.js";
import * as template from "../core/template.js";
import * as calendar from "./calendar.js";
import CalendarDay from "./CalendarDay.js";
import CalendarDayNamesHeader from "./CalendarDayNamesHeader.js";
import CalendarDays from "./CalendarDays.js";
import CalendarElementMixin from "./CalendarElementMixin.js";
import CalendarMonthYearHeader from "./CalendarMonthYearHeader.js";
import * as internal from "./internal.js";

const Base = CalendarElementMixin(ReactiveElement);

/**
 * A single calendar month, optimized for a given locale
 *
 * [A default representation for the current month in browser's default locale](/demos/calendarMonth.html)
 *
 * Given a reference `date` property, this component will show a calendar
 * representation of that month. To the extent possible, this representation is
 * sensitive to a specified locale: the names of the months and days of the week
 * will be in the appropriate language, and the day(s) associated with the
 * weekend in that locale will also be indicated.
 *
 * @inherits ReactiveElement
 * @mixes CalendarElementMixin
 * @part {CalendarDay} day - any of the day elements in the month grid
 * @part {CalendarDayNamesHeader} day-names-header - the column header showing the names of the days
 * @part {CalendarDays} month-days - the grid of days for the month
 * @part {CalendarMonthYearHeader} month-year-header - the calendar header showing the month and/or year
 * @part day-name - any of the names for the days of the week
 */
class CalendarMonth extends Base {
  /**
   * Returns the day element corresponding to the given date, or null if the
   * date falls outside the range currently covered by this calendar.
   *
   * @param {Date} date - the date to search for
   * @returns {Element|null}
   */
  dayElementForDate(date) {
    /** @type {any} */
    const monthDays = this[internal.ids].monthDays;
    return (
      monthDays &&
      "dayElementForDate" in monthDays &&
      monthDays.dayElementForDate(date)
    );
  }

  /**
   * The class or tag used to create the header showing the
   * day names.
   *
   * @type {PartDescriptor}
   * @default CalendarDayNamesHeader
   */
  get dayNamesHeaderPartType() {
    return this[internal.state].dayNamesHeaderPartType;
  }
  set dayNamesHeaderPartType(dayNamesHeaderPartType) {
    this[internal.setState]({ dayNamesHeaderPartType });
  }

  /**
   * The class or tag used to create the `day` parts – the set of
   * days shown in the calendar grid.
   *
   * @type {PartDescriptor}
   * @default CalendarDay
   */
  get dayPartType() {
    return this[internal.state].dayPartType;
  }
  set dayPartType(dayPartType) {
    this[internal.setState]({ dayPartType });
  }

  /**
   * Returns the day elements contained by this calendar. Note that this may
   * include days from the previous/next month that fall in the same week as
   * the first/last day of the present month.
   *
   * @type {Element[]}
   */
  get days() {
    return this[internal.shadowRoot]
      ? /** @type {any} */ (this[internal.ids].monthDays).days
      : [];
  }

  /**
   * The format used to render the day names in the week days header.
   *
   * The allowable formats are the same as the `weekday` formats in
   * [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat).
   *
   * @type {('long'|'narrow'|'short')}
   * @default 'short'
   */
  get daysOfWeekFormat() {
    return this[internal.state].daysOfWeekFormat;
  }
  set daysOfWeekFormat(daysOfWeekFormat) {
    this[internal.setState]({ daysOfWeekFormat });
  }

  get [internal.defaultState]() {
    return Object.assign(super[internal.defaultState], {
      date: calendar.today(),
      dayNamesHeaderPartType: CalendarDayNamesHeader,
      dayPartType: CalendarDay,
      daysOfWeekFormat: "short",
      monthDaysPartType: CalendarDays,
      monthFormat: "long",
      monthYearHeaderPartType: CalendarMonthYearHeader,
      showCompleteWeeks: false,
      showSelectedDay: false,
      yearFormat: "numeric",
    });
  }

  /**
   * The format used to render the month name.
   *
   * The allowable formats are the same as the `month` formats in
   * [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat).
   *
   * @type {('numeric'|'2-digit'|'long'|'short'|'narrow')}
   * @default 'long'
   */
  get monthFormat() {
    return this[internal.state].monthFormat;
  }
  set monthFormat(monthFormat) {
    this[internal.setState]({ monthFormat });
  }

  /**
   * The class or tag used to create the grid of days.
   *
   * @type {PartDescriptor}
   * @default CalendarDays
   */
  get monthDaysPartType() {
    return this[internal.state].monthDaysPartType;
  }
  set monthDaysPartType(monthDaysPartType) {
    this[internal.setState]({ monthDaysPartType });
  }

  /**
   * The class or tag used to create the header showing the
   * month and year.
   *
   * @type {PartDescriptor}
   * @default CalendarMonthYearHeader
   */
  get monthYearHeaderPartType() {
    return this[internal.state].monthYearHeaderPartType;
  }
  set monthYearHeaderPartType(monthYearHeaderPartType) {
    this[internal.setState]({ monthYearHeaderPartType });
  }

  [internal.render](/** @type {ChangedFlags} */ changed) {
    super[internal.render](changed);
    renderParts(this[internal.shadowRoot], this[internal.state], changed);

    if (changed.dayPartType || changed.monthDaysPartType) {
      /** @type {any} */ (this[internal.ids].monthDays).dayPartType = this[
        internal.state
      ].dayPartType;
    }

    if (
      changed.locale ||
      changed.monthDaysPartType ||
      changed.monthYearHeaderPartType ||
      changed.dayNamesHeaderPartType
    ) {
      const locale = this[internal.state].locale;
      /** @type {any} */ (this[internal.ids].monthDays).locale = locale;
      /** @type {any} */ (this[internal.ids].monthYearHeader).locale = locale;
      /** @type {any} */ (this[internal.ids].dayNamesHeader).locale = locale;
    }

    if (changed.date || changed.monthDaysPartType) {
      const { date } = this[internal.state];
      if (date) {
        const startDate = calendar.firstDateOfMonth(date);
        const endDate = calendar.lastDateOfMonth(date);
        const dayCount = endDate.getDate();
        Object.assign(this[internal.ids].monthDays, {
          date,
          dayCount,
          startDate,
        });
        /** @type {any} */ (this[internal.ids]
          .monthYearHeader).date = calendar.firstDateOfMonth(date);
      }
    }

    if (changed.daysOfWeekFormat || changed.dayNamesHeaderPartType) {
      const { daysOfWeekFormat } = this[internal.state];
      /** @type {any} */ (this[internal.ids]
        .dayNamesHeader).format = daysOfWeekFormat;
    }

    if (changed.showCompleteWeeks || changed.monthDaysPartType) {
      const { showCompleteWeeks } = this[internal.state];
      /** @type {any} */ (this[internal.ids]
        .monthDays).showCompleteWeeks = showCompleteWeeks;
    }

    if (changed.showSelectedDay || changed.monthDaysPartType) {
      const { showSelectedDay } = this[internal.state];
      /** @type {any} */ (this[internal.ids]
        .monthDays).showSelectedDay = showSelectedDay;
    }

    if (changed.monthFormat || changed.monthYearHeaderPartType) {
      const { monthFormat } = this[internal.state];
      /** @type {any} */ (this[internal.ids]
        .monthYearHeader).monthFormat = monthFormat;
    }

    if (changed.yearFormat || changed.monthYearHeaderPartType) {
      const { yearFormat } = this[internal.state];
      /** @type {any} */ (this[internal.ids]
        .monthYearHeader).yearFormat = yearFormat;
    }
  }

  get showCompleteWeeks() {
    return this[internal.state].showCompleteWeeks;
  }
  set showCompleteWeeks(showCompleteWeeks) {
    this[internal.setState]({ showCompleteWeeks });
  }

  get showSelectedDay() {
    return this[internal.state].showSelectedDay;
  }
  set showSelectedDay(showSelectedDay) {
    this[internal.setState]({ showSelectedDay });
  }

  get [internal.template]() {
    const result = template.html`

    `;
    renderParts(result.content, this[internal.state]);
    return result;
  }

  /**
   * The format used to render the year.
   *
   * The allowable formats are the same as the `year` formats in
   * [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat).
   *
   * @type {('numeric'|'2-digit')}
   * @default 'numeric'
   */
  get yearFormat() {
    return this[internal.state].yearFormat;
  }
  set yearFormat(yearFormat) {
    this[internal.setState]({ yearFormat });
  }
}

/**
 * Render parts for the template or an instance.
 *
 * @private
 * @param {DocumentFragment} root
 * @param {PlainObject} state
 * @param {ChangedFlags} [changed]
 */
function renderParts(root, state, changed) {
  if (!changed || changed.dayNamesHeaderPartType) {
    const { dayNamesHeaderPartType } = state;
    const dayNamesHeader = root.getElementById("dayNamesHeader");
    if (dayNamesHeader) {
      template.transmute(dayNamesHeader, dayNamesHeaderPartType);
    }
  }
  if (!changed || changed.monthYearHeaderPartType) {
    const { monthYearHeaderPartType } = state;
    const monthYearHeader = root.getElementById("monthYearHeader");
    if (monthYearHeader) {
      template.transmute(monthYearHeader, monthYearHeaderPartType);
    }
  }
  if (!changed || changed.monthDaysPartType) {
    const { monthDaysPartType } = state;
    const monthDays = root.getElementById("monthDays");
    if (monthDays) {
      template.transmute(monthDays, monthDaysPartType);
    }
  }
}

export default CalendarMonth;
<|endoftext|>


<commit_before>//= require iso8601
//= require jquery/plugins/jquery.qtip2.min
//= require jquery/plugins/jquery.multiselect.min
//= require jquery/plugins/jquery.chooser
//= require jquery/plugins/jquery-ui-timepicker-addon
//= require jquery/plugins/jquery.imagesloaded.min
//= require jquery/plugins/jquery.timeago
//= require jquery/plugins/jquery.scrollTo-min
//= require jquery/plugins/jquery.string.1.0
//= require i18n.min
//= require translations
//= require rails
//= require application
//= require inaturalist
//= require jquery/plugins/inat/taxon_selectors<commit_msg>Added missing JS to application bundle<commit_after>//= require iso8601
//= require jquery/plugins/jquery.qtip2.min
//= require jquery/plugins/jquery.multiselect.min
//= require jquery/plugins/jquery.ui.autocomplete.html.js
//= require jquery/plugins/jquery.chooser
//= require jquery/plugins/jquery-ui-timepicker-addon
//= require jquery/plugins/jquery.imagesloaded.min
//= require jquery/plugins/jquery.timeago
//= require jquery/plugins/jquery.scrollTo-min
//= require jquery/plugins/jquery.string.1.0
//= require i18n.min
//= require translations
//= require rails
//= require application
//= require inaturalist
//= require jquery/plugins/inat/taxon_selectors<|endoftext|>


<commit_before>const { readFile } = require('fs');
const {
	app: App,
	BrowserWindow,
	Menu,
	Tray
} = require('electron');
global.APP_PATH = App.getAppPath();
global.AUTH_FILE = global.APP_PATH + "/auth.json";
global.CLIP_FILE = global.APP_PATH + "/clipboard.png";
global.OPT_FILE = global.APP_PATH + "/settings.json";
const OPT = require(global.OPT_FILE);
const PKG = require("./package.json");
const Runner = require("./runner.js");
const Pug = require('electron-pug')({ pretty: true }, {
	version: PKG.version,
	OPT: OPT,
	L: Runner.L
});

let mainWindow;

App.on('ready', main);
App.on('window-all-closed', () => {
	if(process.platform != 'darwin') App.quit();
});
App.on('activate', () => {
	if(mainWindow === null) main();
});
Runner.send = (...argv) => {
	mainWindow.webContents.send.apply(mainWindow.webContents, argv);
};

function main(){
	Menu.setApplicationMenu(Menu.buildFromTemplate(Runner.MAIN_MENU));

	const V = `${PKG['name']} ${PKG['version']}`;
	let winOpt = {
		title: `${V} - Now Loading`,
		width: OPT['width'] || 900,
		height: OPT['height'] || 600,
		icon: `file://${__dirname}/views/img/logo.ico`
	};

	mainWindow = new BrowserWindow(winOpt);
	mainWindow.loadURL(`file://${__dirname}/views/index.pug`);
}<commit_msg>changed logo path<commit_after>const { readFile } = require('fs');
const {
	app: App,
	BrowserWindow,
	Menu,
	Tray
} = require('electron');
global.APP_PATH = App.getAppPath();
global.AUTH_FILE = global.APP_PATH + "/auth.json";
global.CLIP_FILE = global.APP_PATH + "/clipboard.png";
global.OPT_FILE = global.APP_PATH + "/settings.json";
const OPT = require(global.OPT_FILE);
const PKG = require("./package.json");
const Runner = require("./runner.js");
const Pug = require('electron-pug')({ pretty: true }, {
	version: PKG.version,
	OPT: OPT,
	L: Runner.L
});

let mainWindow;

App.on('ready', main);
App.on('window-all-closed', () => {
	if(process.platform != 'darwin') App.quit();
});
App.on('activate', () => {
	if(mainWindow === null) main();
});
Runner.send = (...argv) => {
	mainWindow.webContents.send.apply(mainWindow.webContents, argv);
};

function main(){
	Menu.setApplicationMenu(Menu.buildFromTemplate(Runner.MAIN_MENU));

	const V = `${PKG['name']} ${PKG['version']}`;
	let winOpt = {
		title: `${V} - Now Loading`,
		width: OPT['width'] || 900,
		height: OPT['height'] || 600,
		icon: `./views/img/logo.ico`
	};

	mainWindow = new BrowserWindow(winOpt);
	mainWindow.loadURL(`file://${__dirname}/views/index.pug`);
}<|endoftext|>


<commit_before>/*
NOTICE
The code herein was taken and modified in part from the Twitter open source portal - https://github.com/twitter/twitter.github.com
Modifications were made to include multiple organization accounts and display them differently
 */

(function () {

  // Put custom repo descriptions in this object, keyed by repo name.
  var repoDescriptions = {
    eas : "The Enterprise Addressing System (EAS) is an open source, web-based application that allows employees of government agencies to query, update, and retire street addresses.",
    CycleTracksWebsite : "Simple db and php code to catch data from iOS and Android CycleTracks apps."
  };

  // readOrgs() populates these three data structures from index.html.
  // Putting the raw data in index.html is more SEO-friendly since the
  // links are available in the original HTML as opposed to being
  // "injected" by the javascript at render-time.
  var orgs = [];
  var orgNameMap = {};
  var orgUrls = {};

  var repoUrls = {};

  // Extract the organization username from the URL.
  function urlToOrg(url) {
    // Remove the trailing slash, if present.
    if (url[url.length - 1] == "/") {
      url = url.slice(0, -1);
    }
    var index = url.lastIndexOf("/") + 1;
    var org = url.slice(index);

    return org;
  }

  // Read the organization data from index.html.
  function readOrgs() {
    var anchors = $("#orgs li a");
    for (var i = 0; i < anchors.length; i++) {
      var anchor = $(anchors[i]);
      var orgName = anchor.text();
      var url = anchor.attr("href");
      var org = urlToOrg(url).toLowerCase();

      orgs.push(org);
      orgNameMap[org] = orgName;
      orgUrls[org] = url;
    }
  }

  function repoUrl(repo) {
    return repoUrls[repo.name] || repo.html_url;
  }

  function repoDescription(repo) {
    return repoDescriptions[repo.name] || repo.description;
  }

  function repoToOrgName(repo) {
    return orgNameMap[repo.owner.login.toLowerCase()] || repo.name;
  }

  function insertRepo(repo) {
    var description = repoDescription(repo);
    var $item = $("<div>").addClass("col-sm-4 repo");
    var $link = $("<a>").attr("href", repoUrl(repo)).attr("id",repo.id).appendTo($item);
    var $panel = $("<div>").addClass("panel panel-default " + (repo.language || '').toLowerCase()).appendTo($link);
    var $heading = $("<div>").addClass("panel-heading").appendTo($panel);
    $heading.append($("<h3>").addClass("panel-title").text(repo.name));
    $heading.append($("<small>").text(repo.language || ''));
    var $body = $("<div>").addClass("panel-body").appendTo($panel);
    var $para = $("<p>");
    if (description) {
      $para.text(description);
    } else {
      $para.append($("<em>").text("No description provided."));
    }
    $body.append($para);
    $panel.append($("<div>").addClass("panel-footer " + (repo.owner.login || '').toLowerCase()).text(repoToOrgName(repo)));
    $item.appendTo("#repos");
  }

  function insertRepos(repos) {
    // $("#num-repos").text(repos.length);

    // Convert pushed_at to Date.
    $.each(repos, function (i, repo) {
      repo.pushed_at = new Date(repo.pushed_at);

      var weekHalfLife = 1.146 * Math.pow(10, -9);

      var pushDelta = (new Date) - Date.parse(repo.pushed_at);
      var createdDelta = (new Date) - Date.parse(repo.created_at);

      var weightForPush = 1;
      var weightForWatchers = 1.314 * Math.pow(10, 7);

      repo.hotness = weightForPush * Math.pow(Math.E, -1 * weekHalfLife * pushDelta);
      repo.hotness += weightForWatchers * repo.watchers / createdDelta;
    });

    // Sort by highest # of watchers.
    repos.sort(function (a, b) {
      if (a.hotness < b.hotness) return 1;
      if (b.hotness < a.hotness) return -1;
      return 0;
    });

    $.each(repos, function (i, repo) {
      insertRepo(repo);
    });

    // Sort by most-recently pushed to.
    repos.sort(function (a, b) {
      if (a.pushed_at < b.pushed_at) return 1;
      if (b.pushed_at < a.pushed_at) return -1;
      return 0;
    });
    /*
    $.each(repos.slice(0, 3), function (i, repo) {
      addRecentlyUpdatedRepo(repo);
    });
    */
  }

  function mapBitbucket(result) {
    // Map required values from Bitbucket API to GitHub equivalent for easier processing
    $.each(result.values, function(i,repo){
      repo.pushed_at = repo.updated_on;
      repo.created_at = repo.created_on;
      repo.id = repo.full_name;
      repo.owner.login = repo.owner.username;
      repo.html_url = repo.links.html.href;
      $.getJSON(repo.links.watchers.href + "?callback=?", function(result){
        repo.watchers = result.values.length;
      });
    });
    return result;
  }

  function addRepos(orgIdx, repos, page) {
    orgIdx = orgIdx || 0;
    repos = repos || [];
    page = page || 1;

    var org = orgs[orgIdx];
    var orgUrl = orgUrls[org];
    var urlPrefix = orgUrl.slice(0, 21);
    var bitbucket = (urlPrefix == "https://bitbucket.org");

    if (!bitbucket) {
      var uri = "https://api.github.com/orgs/" + org + "/repos?callback=?"
          + "&per_page=100"
          + "&page=" + page;
    } else {
      var uri = "https://bitbucket.org/api/2.0/repositories/" + org + "?callback=?";
    }

    $.getJSON(uri, function (result) {
      if ((result.values && result.values.length > 0) || (result.data && result.data.length > 0)) {
        if (bitbucket) {
          result = mapBitbucket(result);
          repos = repos.concat(result.values);
          addRepos(orgIdx + 1, repos);
        } else {
          repos = repos.concat(result.data);
          addRepos(orgIdx, repos, page + 1);
        }
      }
      else if (orgs.length > orgIdx + 1) {
        addRepos(orgIdx + 1, repos);
      }
      else {
        insertRepos(repos);
      }
    });
  }

  readOrgs();
  addRepos();
})();
<commit_msg>Simplify addRepos() a bit more.<commit_after>/*
NOTICE
The code herein was taken and modified in part from the Twitter open source portal - https://github.com/twitter/twitter.github.com
Modifications were made to include multiple organization accounts and display them differently
 */

(function () {

  // Put custom repo descriptions in this object, keyed by repo name.
  var repoDescriptions = {
    eas : "The Enterprise Addressing System (EAS) is an open source, web-based application that allows employees of government agencies to query, update, and retire street addresses.",
    CycleTracksWebsite : "Simple db and php code to catch data from iOS and Android CycleTracks apps."
  };

  // readOrgs() populates these three data structures from index.html.
  // Putting the raw data in index.html is more SEO-friendly since the
  // links are available in the original HTML as opposed to being
  // "injected" by the javascript at render-time.
  var orgs = [];
  var orgNameMap = {};
  var orgUrls = {};

  var repoUrls = {};

  // Extract the organization username from the URL.
  function urlToOrg(url) {
    // Remove the trailing slash, if present.
    if (url[url.length - 1] == "/") {
      url = url.slice(0, -1);
    }
    var index = url.lastIndexOf("/") + 1;
    var org = url.slice(index);

    return org;
  }

  // Read the organization data from index.html.
  function readOrgs() {
    var anchors = $("#orgs li a");
    for (var i = 0; i < anchors.length; i++) {
      var anchor = $(anchors[i]);
      var orgName = anchor.text();
      var url = anchor.attr("href");
      var org = urlToOrg(url).toLowerCase();

      orgs.push(org);
      orgNameMap[org] = orgName;
      orgUrls[org] = url;
    }
  }

  function repoUrl(repo) {
    return repoUrls[repo.name] || repo.html_url;
  }

  function repoDescription(repo) {
    return repoDescriptions[repo.name] || repo.description;
  }

  function repoToOrgName(repo) {
    return orgNameMap[repo.owner.login.toLowerCase()] || repo.name;
  }

  function insertRepo(repo) {
    var description = repoDescription(repo);
    var $item = $("<div>").addClass("col-sm-4 repo");
    var $link = $("<a>").attr("href", repoUrl(repo)).attr("id",repo.id).appendTo($item);
    var $panel = $("<div>").addClass("panel panel-default " + (repo.language || '').toLowerCase()).appendTo($link);
    var $heading = $("<div>").addClass("panel-heading").appendTo($panel);
    $heading.append($("<h3>").addClass("panel-title").text(repo.name));
    $heading.append($("<small>").text(repo.language || ''));
    var $body = $("<div>").addClass("panel-body").appendTo($panel);
    var $para = $("<p>");
    if (description) {
      $para.text(description);
    } else {
      $para.append($("<em>").text("No description provided."));
    }
    $body.append($para);
    $panel.append($("<div>").addClass("panel-footer " + (repo.owner.login || '').toLowerCase()).text(repoToOrgName(repo)));
    $item.appendTo("#repos");
  }

  function insertRepos(repos) {
    // $("#num-repos").text(repos.length);

    // Convert pushed_at to Date.
    $.each(repos, function (i, repo) {
      repo.pushed_at = new Date(repo.pushed_at);

      var weekHalfLife = 1.146 * Math.pow(10, -9);

      var pushDelta = (new Date) - Date.parse(repo.pushed_at);
      var createdDelta = (new Date) - Date.parse(repo.created_at);

      var weightForPush = 1;
      var weightForWatchers = 1.314 * Math.pow(10, 7);

      repo.hotness = weightForPush * Math.pow(Math.E, -1 * weekHalfLife * pushDelta);
      repo.hotness += weightForWatchers * repo.watchers / createdDelta;
    });

    // Sort by highest # of watchers.
    repos.sort(function (a, b) {
      if (a.hotness < b.hotness) return 1;
      if (b.hotness < a.hotness) return -1;
      return 0;
    });

    $.each(repos, function (i, repo) {
      insertRepo(repo);
    });

    // Sort by most-recently pushed to.
    repos.sort(function (a, b) {
      if (a.pushed_at < b.pushed_at) return 1;
      if (b.pushed_at < a.pushed_at) return -1;
      return 0;
    });
    /*
    $.each(repos.slice(0, 3), function (i, repo) {
      addRecentlyUpdatedRepo(repo);
    });
    */
  }

  function mapBitbucket(result) {
    // Map required values from Bitbucket API to GitHub equivalent for easier processing
    $.each(result.values, function(i,repo){
      repo.pushed_at = repo.updated_on;
      repo.created_at = repo.created_on;
      repo.id = repo.full_name;
      repo.owner.login = repo.owner.username;
      repo.html_url = repo.links.html.href;
      $.getJSON(repo.links.watchers.href + "?callback=?", function(result){
        repo.watchers = result.values.length;
      });
    });
    return result;
  }

  function addRepos(orgIdx, repos, page) {
    orgIdx = orgIdx || 0;
    repos = repos || [];
    page = page || 1;

    var org = orgs[orgIdx];
    var orgUrl = orgUrls[org];
    var urlPrefix = orgUrl.slice(0, 21);
    var bitbucket = (urlPrefix == "https://bitbucket.org");

    if (bitbucket) {
      var uri = "https://bitbucket.org/api/2.0/repositories/" + org + "?callback=?";
    } else {
      var uri = "https://api.github.com/orgs/" + org + "/repos?callback=?&per_page=100&page=" + page;
    }

    $.getJSON(uri, function (result) {
      if ((result.values && result.values.length > 0) || (result.data && result.data.length > 0)) {
        var newRepos;
        if (bitbucket) {
          result = mapBitbucket(result);
          newRepos = result.values;
          orgIdx += 1;
          page = 1;
        } else {
          newRepos = result.data;
          page += 1;
        }
        repos = repos.concat(newRepos);
        addRepos(orgIdx, repos, page);
      }
      else if (orgs.length > orgIdx + 1) {
        addRepos(orgIdx + 1, repos);
      }
      else {
        insertRepos(repos);
      }
    });
  }

  readOrgs();
  addRepos();
})();
<|endoftext|>


<commit_before>import React from 'react';
import { shallow } from 'enzyme';
import Checkbox from '..';

describe('Checkbox', () => {
  it('responses hover events', () => {
    const onMouseEnter = jest.fn();
    const onMouseLeave = jest.fn();

    const wrapper = shallow(
      <Checkbox
        onMouseEnter={onMouseEnter}
        onMouseLeave={onMouseLeave}
      />
    );

    wrapper.simulate('mouseenter');
    expect(onMouseEnter).toHaveBeenCalled();

    wrapper.simulate('mouseleave');
    expect(onMouseLeave).toHaveBeenCalled();
  });
});
<commit_msg>Add test case for Checkbox<commit_after>import React from 'react';
import { shallow, mount } from 'enzyme';
import Checkbox from '..';

describe('Checkbox', () => {
  it('responses hover events', () => {
    const onMouseEnter = jest.fn();
    const onMouseLeave = jest.fn();

    const wrapper = shallow(
      <Checkbox
        onMouseEnter={onMouseEnter}
        onMouseLeave={onMouseLeave}
      />
    );

    wrapper.simulate('mouseenter');
    expect(onMouseEnter).toHaveBeenCalled();

    wrapper.simulate('mouseleave');
    expect(onMouseLeave).toHaveBeenCalled();
  });

  it('CheckGroup should work', () => {
    const onChange = jest.fn();
    const wrapper = mount(
      <Checkbox.Group options={['Apple', 'Pear', 'Orange']} onChange={onChange} />
    );
    wrapper.find('.ant-checkbox-input').at(0).simulate('change');
    expect(onChange).toBeCalledWith(['Apple']);
    wrapper.find('.ant-checkbox-input').at(1).simulate('change');
    expect(onChange).toBeCalledWith(['Apple', 'Pear']);
    wrapper.find('.ant-checkbox-input').at(2).simulate('change');
    expect(onChange).toBeCalledWith(['Apple', 'Pear', 'Orange']);
    wrapper.find('.ant-checkbox-input').at(1).simulate('change');
    expect(onChange).toBeCalledWith(['Apple', 'Orange']);
  });
});
<|endoftext|>


<commit_before>import Typography from 'typography';

const typography = new Typography({
  baseFontSize: '20px',
  baseLineHeight: 1.2,
  headerFontFamily: ['sans-serif'],
  headerLineHeight: 1.5,
  bodyFontFamily: ['sans-serif'],
  bodyWeight: '300',
});

export default typography;
<commit_msg>some revise<commit_after>import Typography from 'typography';

const typography = new Typography({
  baseFontSize: '20px',
  baseLineHeight: 1.4,
  headerFontFamily: ['sans-serif'],
  headerLineHeight: 1.5,
  bodyFontFamily: ['sans-serif'],
  bodyWeight: '300',
});

export default typography;
<|endoftext|>


<commit_before>$(document).ready(function() {
  var EXCLUDE = /\.(xml|txt|jpg|png|avi|mp3|pdf|mpg)$/,
    HN_BASE = 'https://news.ycombinator.com/',
    HNSEARCH_API_URL = 'https://api.thriftdb.com/api.hnsearch.com/items/_search?sortby=create_ts+desc&',
    HNSEARCH_PARAM = 'filter[fields][url][]=';

  var port = chrome.extension.connect({}),
    callbacks = [];

  port.onMessage.addListener(function(msg) {
    callbacks[msg.id](msg.text);
    delete callbacks[msg.id];
  });

  function doXHR(params, callback) {
    params.id = callbacks.push(callback) - 1;
    port.postMessage(params);
  }

  function searchHN() {
    // don't run on frames or iframes.
    // from http://stackoverflow.com/questions/1535404/how-to-exclude-iframe-in-greasemonkey
    if (window.top != window.self) { return; }

    var urls = [window.location.href];
    if (EXCLUDE.test(urls[0])) { return; }

    // Also try different normalizationa of trailing slashes
    if ((/\/$/).test(urls[0])) { urls.push(urls[0].replace(/\/$/, '')); }
    if (!(/\?/).test(urls[0]) && !(/(\.\w{2,4}|\/)$/).test(urls[0])) { urls.push(urls[0] + '/'); }

    var queryURL = HNSEARCH_API_URL + HNSEARCH_PARAM + urls.map(encodeURIComponent).join('&' + HNSEARCH_PARAM);
    doXHR({'action': 'get', 'url': queryURL}, function(response) {
      // JSON.parse will not evaluate any malicious JavaScript embedded into JSON
      var data = JSON.parse(response);

      // No results, maybe it's too new
      if (data.results.length < 1) {
        doXHR({'action':'get','url': HN_BASE + "newest"}, function(response) {
          searchNewestHN(response);
        });
        return;
      }

      // If there is a result, create the orange tab and panel
      var foundItem = data.results[0].item;
      createPanel(HN_BASE + 'item?id=' + foundItem.id);
    });
  }

  function searchNewestHN(html) {
    var titleAnchor = $('a[href=\'' + window.location.href.replace(/'/g, "\\'") + '\']', html),
      linkAnchor = titleAnchor.parent().parent().next().find('a').get(1);
    if (linkAnchor) {
      createPanel(HN_BASE + $(linkAnchor).attr('href'));
    }
  }

  function toggleElement(elemName) {
    var element = $(elemName);
    if (element.style.display == 'none') {
      element.style.display = 'block';
      return;
    }
    element.style.display = 'none';
  }

  function createPanel(HNurl) {
    if ($(".HNembed").length > 0) { return; } // avoid situations where multiple results might be triggered.

    var HNembed = $("
").attr({'id' : 'HNembed'});
    var HNsite = $("").attr({'id' : 'HNsite', 'src' : 'about: blank'});
    var HNtab = $("
HackerNews
").attr({'id' : 'HNtab'});

    var panelTitle = ">>> Hacker News >>>";
    var HNtitle = $("" + panelTitle + "").attr({'id' : 'HNtitle'});
    var HNheader = $("
").attr({'id' : 'HNheader'});

    $(window).resize(fixIframeHeight);

    function fixIframeHeight() {
      HNembed.height($(window).height());
      HNsite.height(HNembed.height()-20);
    }

    function togglePanel() {
      var openPanel = HNtab.is(":visible");

      var embedPosition = openPanel ? "0px" : "-700px";
      var tabPosition = openPanel ? "-25px" : "0px";

      if (openPanel) {
        fixIframeHeight();
        HNtab.animate({right: tabPosition}, 150, "linear", function() {
          HNembed.show();
          HNtab.hide();
          HNembed.animate({right: embedPosition},400,"linear");
        });
      } else {
        HNembed.animate({right: embedPosition}, 400, "linear", function() {
          HNtab.show();
          HNembed.hide();
          HNtab.animate({right: tabPosition}, 150, "linear");
        });
      }
    }

    HNheader.click(togglePanel);
    HNtab.click(togglePanel);

    HNheader.append(HNtitle);

    HNembed.append(HNheader);
    HNembed.append(HNsite);
    HNembed.hide();

    $('body').append(HNtab);
    $('body').append(HNembed);

    doXHR({'action': 'get', 'url': HNurl}, function(response) {
      var doc = HNsite.get(0).contentDocument;
      response = response.replace(//, '<base target="_blank" href="'+HN_BASE+'"/>');
      doc.open();
      doc.write(response);
      doc.close();
    });
  }

  searchHN();

});<commit_msg>fixes issue #2<commit_after>$(document).ready(function() {
  var EXCLUDE = /\.(xml|txt|jpg|png|avi|mp3|pdf|mpg)$/,
    HN_BASE = 'https://news.ycombinator.com/',
    HNSEARCH_API_URL = 'https://api.thriftdb.com/api.hnsearch.com/items/_search?sortby=create_ts+desc&',
    HNSEARCH_PARAM = 'filter[fields][url][]=';

  var port = chrome.extension.connect({}),
    callbacks = [];

  port.onMessage.addListener(function(msg) {
    callbacks[msg.id](msg.text);
    delete callbacks[msg.id];
  });

  function doXHR(params, callback) {
    params.id = callbacks.push(callback) - 1;
    port.postMessage(params);
  }

  function searchHN() {
    // don't run on frames or iframes.
    // from http://stackoverflow.com/questions/1535404/how-to-exclude-iframe-in-greasemonkey
    if (window.top != window.self) { return; }

    var urls = [window.location.href];
    if (EXCLUDE.test(urls[0])) { return; }

    // Also try different normalizationa of trailing slashes
    if ((/\/$/).test(urls[0])) { urls.push(urls[0].replace(/\/$/, '')); }
    if (!(/\?/).test(urls[0]) && !(/(\.\w{2,4}|\/)$/).test(urls[0])) { urls.push(urls[0] + '/'); }

    var queryURL = HNSEARCH_API_URL + HNSEARCH_PARAM + urls.map(encodeURIComponent).join('&' + HNSEARCH_PARAM);
    doXHR({'action': 'get', 'url': queryURL}, function(response) {
      // JSON.parse will not evaluate any malicious JavaScript embedded into JSON
      var data = JSON.parse(response);

      // No results, maybe it's too new
      if (data.results.length < 1) {
        doXHR({'action':'get','url': HN_BASE + "newest"}, function(response) {
          searchNewestHN(response);
        });
        return;
      }

      // If there is a result, create the orange tab and panel
      var foundItem = data.results[0].item;
      createPanel(HN_BASE + 'item?id=' + foundItem.id);
    });
  }

  function searchNewestHN(html) {
    html = html.replace(/<img src="[^"]+"/g, '<img');
    var titleAnchor = $('a[href=\'' + window.location.href.replace(/'/g, "\\'") + '\']', html),
      linkAnchor = titleAnchor.parent().parent().next().find('a').get(1);
    if (linkAnchor) {
      createPanel(HN_BASE + $(linkAnchor).attr('href'));
    }
  }

  function toggleElement(elemName) {
    var element = $(elemName);
    if (element.style.display == 'none') {
      element.style.display = 'block';
      return;
    }
    element.style.display = 'none';
  }

  function createPanel(HNurl) {
    if ($(".HNembed").length > 0) { return; } // avoid situations where multiple results might be triggered.

    var HNembed = $("
").attr({'id' : 'HNembed'});
    var HNsite = $("").attr({'id' : 'HNsite', 'src' : 'about: blank'});
    var HNtab = $("
HackerNews
").attr({'id' : 'HNtab'});

    var panelTitle = ">>> Hacker News >>>";
    var HNtitle = $("" + panelTitle + "").attr({'id' : 'HNtitle'});
    var HNheader = $("
").attr({'id' : 'HNheader'});

    $(window).resize(fixIframeHeight);

    function fixIframeHeight() {
      HNembed.height($(window).height());
      HNsite.height(HNembed.height()-20);
    }

    function togglePanel() {
      var openPanel = HNtab.is(":visible");

      var embedPosition = openPanel ? "0px" : "-700px";
      var tabPosition = openPanel ? "-25px" : "0px";

      if (openPanel) {
        fixIframeHeight();
        HNtab.animate({right: tabPosition}, 150, "linear", function() {
          HNembed.show();
          HNtab.hide();
          HNembed.animate({right: embedPosition},400,"linear");
        });
      } else {
        HNembed.animate({right: embedPosition}, 400, "linear", function() {
          HNtab.show();
          HNembed.hide();
          HNtab.animate({right: tabPosition}, 150, "linear");
        });
      }
    }

    HNheader.click(togglePanel);
    HNtab.click(togglePanel);

    HNheader.append(HNtitle);

    HNembed.append(HNheader);
    HNembed.append(HNsite);
    HNembed.hide();

    $('body').append(HNtab);
    $('body').append(HNembed);

    doXHR({'action': 'get', 'url': HNurl}, function(response) {
      var doc = HNsite.get(0).contentDocument;
      response = response.replace(//, '<base target="_blank" href="'+HN_BASE+'"/>');
      doc.open();
      doc.write(response);
      doc.close();
    });
  }

  searchHN();

});<|endoftext|>