diff --git a/Gruntfile.js b/Gruntfile.js index 854a0d0..4635ddd 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -16,13 +16,6 @@ "use strict"; module.exports = function (grunt) { - grunt.loadNpmTasks("grunt-contrib-clean"); - grunt.loadNpmTasks("grunt-contrib-copy"); - grunt.loadNpmTasks("fluid-grunt-eslint"); - grunt.loadNpmTasks("grunt-contrib-uglify"); - grunt.loadNpmTasks("grunt-contrib-stylus"); - grunt.loadNpmTasks("grunt-crx"); - grunt.loadNpmTasks("grunt-jsonlint"); var files = { extensionLib: [ @@ -160,9 +153,7 @@ module.exports = function (grunt) { ], extension: [ "extension/src/lib/chromeEvented.js", - "extension/src/lib/chromeNotification.js", "extension/src/lib/domSettingsApplier.js", - "extension/src/lib/extensionHolder.js", "extension/src/lib/highContrast.js", "extension/src/lib/chromeSettings.js", "extension/src/lib/wsConnector.js", @@ -173,19 +164,13 @@ module.exports = function (grunt) { grunt.initConfig({ pkg: grunt.file.readJSON("package.json"), manifest: grunt.file.readJSON("extension/manifest.json"), - jsonlint: { - all: [ - "*.json", - "extension/**/*.json", - "tests/**/*.json" - ] - }, - eslint: { - all: [ - "*.js", - "extension/**/*.js", - "tests/**/*.js" - ] + lintAll: { + sources: { + md: [ "./*.md"], + js: ["./tests/**/*.js", "./extension/**/*.js", "./*.js"], + json: ["./extension/**/*.json", "./*.json"], + other: ["./.*"] + } }, uglify: { options: { @@ -338,7 +323,14 @@ module.exports = function (grunt) { } }); - grunt.registerTask("lint", "Lint the source code", ["jsonlint", "eslint"]); + grunt.loadNpmTasks("grunt-contrib-clean"); + grunt.loadNpmTasks("grunt-contrib-copy"); + grunt.loadNpmTasks("grunt-contrib-uglify"); + grunt.loadNpmTasks("grunt-contrib-stylus"); + grunt.loadNpmTasks("grunt-crx"); + grunt.loadNpmTasks("gpii-grunt-lint-all"); + + grunt.registerTask("lint", "Perform all standard lint checks.", ["lint-all"]); grunt.registerTask("bundle", "Bundle dependencies and source code into a single .min.js file", ["uglify"]); grunt.registerTask("build", "Build the extension so you can start using it unpacked", ["clean", "bundle", "stylus", "copy"]); grunt.registerTask("buildPkg", "Create a .crx package ready to be distributed", ["lint", "build", "crx"]); diff --git a/README.md b/README.md index e915c0d..2c3db52 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # UI Options Plus (UIO+) -User Interface Options Plus (UIO+) allows you to customize websites to match your own personal needs and preferences. Settings for the adaptations can be set via the UIO+ adjuster panel or, if on a GPII (Global Public Inclusive Infrastructure) enable machine, from a keyed in preference set. +User Interface Options Plus (UIO+) allows you to customize websites to match your own personal needs and preferences. +Settings for the adaptations can be set via the UIO+ adjuster panel or, if on a GPII (Global Public Inclusive +Infrastructure) enable machine, from a keyed in preference set. The following adaptations are supported: @@ -14,11 +16,13 @@ The following adaptations are supported: * Reading Mode * Table of Contents * Enhance Inputs -* Dictionary (requires the Google dictionary extension) _**Note**: The ability to apply an adaptation will vary from page to page_ -UI Options Plus is the result of a joint effort of the Inclusive Design Research Centre at OCAD University and the Trace R&D Center at University of Maryland under funding for the FLOE Project from the William and Flora Hewlett Foundation and the National Institute on Disability, Independent Living, and Rehabilitation Research (NIDILRR), Administration for Community Living under grant #90RE5027. +UI Options Plus is the result of a joint effort of the Inclusive Design Research Centre at OCAD University and the Trace +R&D Center at University of Maryland under funding for the FLOE Project from the William and Flora Hewlett Foundation +and the National Institute on Disability, Independent Living, and Rehabilitation Research (NIDILRR), Administration for +Community Living under grant #90RE5027. ## Building the extension @@ -38,12 +42,12 @@ You can also create a development build which creates an unpacked version that i grunt buildDev - If you want to create a [crx](https://developer.chrome.com/extensions/crx) package to be distributed, run: grunt buildPkg -**Note that you need to use a [PEM](http://how2ssl.com/articles/working_with_pem_files/) file to sign the crx package with. This file needs to be called *key.pem* and needs to be placed into the top level folder of this repository.** +**Note that you need to use a [PEM](http://how2ssl.com/articles/working_with_pem_files/) file to sign the crx package +with. This file needs to be called *key.pem* and needs to be placed into the top level folder of this repository.** ## Testing @@ -55,23 +59,21 @@ Run the browser based tests: http://localhost/tests/browser/all-tests.html - - _**NOTE:** Browser tests should be served through a web server. The exact URL may vary._ ## Trying out the extension Requirements: + * [Google Chrome browser](https://www.google.com/chrome/browser/desktop/) Follow these steps if you want to use the unpacked version of the extension: -1. Visit *chrome://extensions* in your browser. Alternatively, open the Chrome menu by clicking the icon to the far right of the Omnibox; the menu's icon is three horizontal bars. Select *Extensions* under the *Tools* menu to open Chrome's extension settings. - +1. Visit *chrome://extensions* in your browser. Alternatively, open the Chrome menu by clicking the icon to the far + right of the Omnibox; the menu's icon is three horizontal bars. Select *Extensions* under the *Tools* menu to open + Chrome's extension settings. 2. Ensure that the *Developer mode* checkbox in the top right-hand corner is checked. - 3. Click *Load unpacked extension* to pop up a file-selection dialog. - 4. Navigate to the directory in which your local copy of the extension lives, and select the *build* folder. Published versions can be installed from the Chrome Web Store. diff --git a/extension/messages/dictionary.json b/extension/messages/dictionary.json deleted file mode 100644 index a3124c9..0000000 --- a/extension/messages/dictionary.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Dictionary", - "description": "Double click a word to show its definition", - "switchOn": "ON", - "switchOff": "OFF" -} diff --git a/extension/src/content_scripts/utils.js b/extension/src/content_scripts/utils.js index caa7c10..64676d0 100644 --- a/extension/src/content_scripts/utils.js +++ b/extension/src/content_scripts/utils.js @@ -21,12 +21,12 @@ * jQuery object with 1 or more elements. If no elements found, it returns * either an empty jQuery object or the default, if a default is provided. * - * @param that {Object} - a fluid.viewComponent - * @param selectors {String|Array} - one or more selector names to search through in the order to try. + * @param {Component} that - a fluid.viewComponent + * @param {String|Array} selectorNames - one or more selector names to search through in the order to try. * The selector names must correspond to selectors in the component's * selectors block. - * @param deflt {Any} - a default value to use if no elements found for any of the selector names - * @returns {Any} - a jQuery object if found. If nothing found, either the deflt value or undefined is returned. + * @param {Any} deflt - a default value to use if no elements found for any of the selector names + * @return {Any} - a jQuery object if found. If nothing found, either the `deflt` value or undefined is returned. */ gpii.chrome.utils.findFirstSelector = function (that, selectorNames, deflt) { selectorNames = fluid.makeArray(selectorNames); diff --git a/extension/src/lib/PrefsEditor.js b/extension/src/lib/PrefsEditor.js index b568f5c..4100dda 100644 --- a/extension/src/lib/PrefsEditor.js +++ b/extension/src/lib/PrefsEditor.js @@ -30,7 +30,6 @@ "preferences.gpii_chrome_prefs_textSize": "settings.fontSize", "preferences.fluid_prefs_speak": "settings.selfVoicingEnabled", "preferences.gpii_chrome_prefs_simplify": "settings.simplifiedUiEnabled", - "preferences.gpii_chrome_prefs_dictionary": "settings.dictionaryEnabled", "preferences.gpii_chrome_prefs_highlight": "settings.selectionTheme", "preferences.gpii_chrome_prefs_clickToSelect": "settings.clickToSelectEnabled" }, @@ -148,6 +147,11 @@ * Sends the prefsEditor.model to the store and fires onSave * Overrides the default writeImpl functionality as all of the model, including the default values, must be sent * to the store. + * + * @param {Component} that - the component + * @param {Object} modelToSave - the model to be written + * + * @return {Promise} promise - a promise that is resolved when the model is saved. */ gpii.chrome.prefs.extensionPanel.writeImpl = function (that, modelToSave) { var promise = fluid.promise(); @@ -206,15 +210,6 @@ } }); - fluid.defaults("gpii.chrome.prefs.panel.dictionary", { - gradeNames: ["fluid.prefs.panel.switchAdjuster"], - preferenceMap: { - "gpii.chrome.prefs.dictionary": { - "model.value": "default" - } - } - }); - fluid.defaults("gpii.chrome.prefs.panel.clickToSelect", { gradeNames: ["fluid.prefs.panel.switchAdjuster"], preferenceMap: { @@ -325,15 +320,6 @@ "message": "%messagePrefix/simplify.json" } }, - "dictionary": { - "type": "gpii.chrome.prefs.dictionary", - "panel": { - "type": "gpii.chrome.prefs.panel.dictionary", - "container": ".flc-prefsEditor-dictionary", - "template": "%templatePrefix/DictionaryPanelTemplate.html", - "message": "%messagePrefix/dictionary.json" - } - }, "selectionHighlight": { "type": "gpii.chrome.prefs.highlight", "classes": { @@ -427,16 +413,6 @@ } }); - fluid.defaults("gpii.chrome.prefs.schemas.dictionary", { - gradeNames: ["fluid.prefs.schemas"], - schema: { - "gpii.chrome.prefs.dictionary": { - "type": "boolean", - "default": false - } - } - }); - fluid.defaults("gpii.chrome.prefs.schemas.clickToSelect", { gradeNames: ["fluid.prefs.schemas"], schema: { diff --git a/extension/src/lib/chromeNotification.js b/extension/src/lib/chromeNotification.js deleted file mode 100644 index 68bf0bf..0000000 --- a/extension/src/lib/chromeNotification.js +++ /dev/null @@ -1,131 +0,0 @@ -/* - * GPII Chrome Extension for Google Chrome - * - * Copyright 2017 OCAD University - * - * Licensed under the New BSD license. You may not use this file except in - * compliance with this license. - * - * You may obtain a copy of the license at - * https://github.com/GPII/gpii-chrome-extension/blob/master/LICENSE.txt - */ - -/* eslint-env node */ -/* global fluid, require */ - -"use strict"; - -var gpii = fluid.registerNamespace("gpii"); -var chrome = chrome || fluid.require("sinon-chrome", require, "chrome"); - -fluid.defaults("gpii.chrome.notification", { - gradeNames: ["fluid.modelComponent", "gpii.chrome.eventedComponent"], - members: { - // must be supplied by an integrator - messageData: {}, - notificationID: { - expander: { - funcName: "fluid.allocateGuid" - } - } - }, - strings: { - title: "", - message: "" - }, - events: { - onClosed: "preventable", - onClicked: "preventable", - onButtonClicked: "preventable", - onNotificationCreated: null, - onNotificationUpdated: null - }, - eventRelayMap: { - "chrome.notifications.onClosed": "onClosed", - "chrome.notifications.onClicked": "onClicked", - "chrome.notifications.onButtonClicked": "onButtonClicked" - }, - // See: https://developer.chrome.com/apps/notifications#type-NotificationOptions for all available options - model: { - type: "basic", - // iconUrl: "relative/path", the result of chrome.extension.getURL("./") to provide the full extension path - title: "{that}.options.strings.title", - message: "{that}.options.strings.message" // may take the form of a string template with values from `messageData` interpolated. - }, - modelRelay: [{ - target: "message", - singleTransform: { - "type": "fluid.transforms.stringTemplate", - "template": "{that}.model.message", - "terms": "{that}.messageData" - } - }, { - target: "iconUrl", - singleTransform: { - "type": "fluid.transforms.free", - "func": "gpii.chrome.notification.transformUrl", - "args": ["{that}.model.iconUrl"] - } - }], - modelListeners: { - "": { - listener: "{that}.update", - excludeSource: ["init"] - } - }, - listeners: { - "onCreate.createNotification": { - listener: "gpii.chrome.notification.create", - priority: "after:bindListeners" - }, - "onDestroy.clear": { - listener: "gpii.chrome.notification.clear", - args: ["{that}.notificationID"] - }, - "onClosed.guard": { - listener: "gpii.chrome.notification.guardEventRelay", - args: ["{that}.notificationID", "{arguments}.0"], - priority: "first" - }, - "onClicked.guard": { - listener: "gpii.chrome.notification.guardEventRelay", - args: ["{that}.notificationID", "{arguments}.0"], - priority: "first" - }, - "onButtonClicked.guard": { - listener: "gpii.chrome.notification.guardEventRelay", - args: ["{that}.notificationID", "{arguments}.0"], - priority: "first" - } - }, - invokers: { - update: { - funcName: "gpii.chrome.notification.update", - args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"] - } - } -}); - -gpii.chrome.notification.transformUrl = function (iconUrl) { - if (fluid.isValue(iconUrl)) { - var prefix = chrome.extension.getURL("./"); - return iconUrl.startsWith(prefix) ? iconUrl : prefix + iconUrl; - } -}; - -// only relay chrome events for this component's notification. -gpii.chrome.notification.guardEventRelay = function (notificationID, id) { - return notificationID === id; -}; - -gpii.chrome.notification.create = function (that) { - chrome.notifications.create(that.notificationID, that.model, that.events.onNotificationCreated.fire); -}; - -gpii.chrome.notification.update = function (that) { - chrome.notifications.update(that.notificationID, that.model, that.events.onNotificationUpdated.fire); -}; - -gpii.chrome.notification.clear = function (notificationID) { - chrome.notifications.clear(notificationID); -}; diff --git a/extension/src/lib/chromeSettings.js b/extension/src/lib/chromeSettings.js index 37229f4..5e2bf3d 100644 --- a/extension/src/lib/chromeSettings.js +++ b/extension/src/lib/chromeSettings.js @@ -18,16 +18,6 @@ fluid.defaults("gpii.chrome.settings", { gradeNames: ["fluid.modelComponent", "gpii.chrome.eventedComponent"], - events: { - onInstalled: "preventable", - onStartup: null, - onLoadExtensionHolders: null, - onCreateNotification: null - }, - eventRelayMap: { - "chrome.runtime.onInstalled": "onInstalled", - "chrome.runtime.onStartup": "onStartup" - }, defaultSettings: { // not all of the following settings are in the common terms yet. // and may need to be updated once they are added there. @@ -39,24 +29,10 @@ fluid.defaults("gpii.chrome.settings", { inputsLargerEnabled: false, // from inputsLargerEnabled selfVoicingEnabled: false, // from selfVoicingEnabled tableOfContentsEnabled: false, // from tableOfContents - dictionaryEnabled: false, // from supportTool simplifiedUiEnabled: false, // from simplifiedUiEnabled syllabificationEnabled: false // from syllabificationEnabled }, components: { - dictionary: { - type: "gpii.chrome.extensionHolder", - createOnEvent: "onLoadExtensionHolders", - options: { - settingName: "dictionary", - extensionId: "mgijmajocgfcbeboacabfgobmjgjcoja", - name: "Google Dictionary (by Google)", - installationUrl: "https://chrome.google.com/webstore/detail/google-dictionary-by-goog/mgijmajocgfcbeboacabfgobmjgjcoja", - model: { - extensionEnabled: "{settings}.model.settings.dictionaryEnabled" - } - } - }, domSettingsApplier: { type: "gpii.chrome.domSettingsApplier", options: { @@ -81,41 +57,8 @@ fluid.defaults("gpii.chrome.settings", { } } }, - dynamicComponents: { - notification: { - type: "gpii.chrome.notification", - createOnEvent: "onCreateNotification", - options: { - members: { - messageData: "{that}.options.managedExtension.options" - }, - managedExtension: "{arguments}.0", - strings: { - title: "GPII notifications", - message: "To use %settingName, please install %name.", - install: "Install from Chrome Web Store" - }, - model: { - type: "basic", - iconUrl: "images/gpii.png", - requireInteraction: true, - buttons: [{ - title: "{that}.options.strings.install" - }] - }, - listeners: { - "onButtonClicked": { - "this": "window", - method: "open", - args: ["{that}.options.managedExtension.options.installationUrl"] - } - } - } - } - }, model: { - settings: "{settings}.options.defaultSettings", // Defaults - promptInstall: true + settings: "{settings}.options.defaultSettings" }, invokers: { updateSettings: { @@ -124,34 +67,10 @@ fluid.defaults("gpii.chrome.settings", { } }, listeners: { - "{wsConnector}.events.onSettingsChange": "{settings}.updateSettings", - "onStartup.updateModel": { - changePath: "promptInstall", - value: false - }, - "onInstalled.loadExtensionHolders": "{that}.events.onLoadExtensionHolders", - "onStartup.loadExtensionHolders": { - listener: "{that}.events.onLoadExtensionHolders", - priority: "after:updateModel" - } - }, - distributeOptions: [{ - record: { - "onExtensionMissing.handle": { - funcName: "gpii.chrome.settings.handleExtensionMissing", - args: ["{settings}", "{that}"] - } - }, - target: "{settings > gpii.chrome.extensionHolder}.options.listeners" - }] + "{wsConnector}.events.onSettingsChange": "{settings}.updateSettings" + } }); gpii.chrome.settings.updateSettings = function (that, settings) { that.applier.change("settings", settings || that.options.defaultSettings); }; - -gpii.chrome.settings.handleExtensionMissing = function (that, extension) { - if (that.model.promptInstall || extension.model.extensionEnabled) { - that.events.onCreateNotification.fire(extension); - } -}; diff --git a/extension/src/lib/extensionHolder.js b/extension/src/lib/extensionHolder.js deleted file mode 100644 index 6a0e3b5..0000000 --- a/extension/src/lib/extensionHolder.js +++ /dev/null @@ -1,183 +0,0 @@ -/* - * GPII Chrome Extension for Google Chrome - * - * Copyright 2016 RtF-US - * Copyright 2017 OCAD University - * - * Licensed under the New BSD license. You may not use this file except in - * compliance with this license. - * - * You may obtain a copy of the license at - * https://github.com/GPII/gpii-chrome-extension/blob/master/LICENSE.txt - */ - -/* eslint-env node */ -/* global fluid */ - -"use strict"; - -var gpii = fluid.registerNamespace("gpii"); -var chrome = chrome || fluid.require("sinon-chrome", require, "chrome"); - -fluid.defaults("gpii.chrome.extensionHolder", { - gradeNames: ["fluid.modelComponent", "gpii.chrome.eventedComponent"], - extensionId: "", - name: "", - installationUrl: "", - members: { - extensionInstance: null - }, - events: { - onError: null, - onExtensionMissing: null, - onSetEnabled: null, - onExtInstalled: "preventable", - onExtUninstalled: "preventable", - onExtEnabled: "preventable", - onExtDisabled: "preventable" - }, - eventRelayMap: { - "chrome.management.onInstalled": "onExtInstalled", - "chrome.management.onUninstalled": "onExtUninstalled", - "chrome.management.onEnabled": "onExtEnabled", - "chrome.management.onDisabled": "onExtDisabled" - }, - model: { - extensionEnabled: undefined - }, - invokers: { - updateEnabledStatus: { - funcName: "gpii.chrome.extensionHolder.updateEnabledStatus", - args: "{that}" - }, - updateModel: { - changePath: "extensionEnabled", - value: "{arguments}.0" - }, - populate: { - funcName: "gpii.chrome.extensionHolder.populate", - args: ["{that}", "{arguments}.0"] - }, - unpopulate: { - funcName: "gpii.chrome.extensionHolder.unpopulate", - args: ["{that}"] - } - }, - listeners: { - "onCreate.populate": { - listener: "{that}.populate", - args: [true], - priority: "after:bindListeners" - }, - "onSetEnabled.complete": { - listener: "gpii.chrome.extensionHolder.setEnabledComplete", - args: "{that}" - }, - "onExtInstalled.guard": { - listener: "gpii.chrome.extensionHolder.guardEventRelay", - args: ["{that}.options.extensionId", "{arguments}.0.id"], - priority: "first" - }, - "onExtInstalled.populate": { - listener: "{that}.populate", - args: [false] - }, - "onExtUninstalled.guard": { - listener: "gpii.chrome.extensionHolder.guardEventRelay", - args: ["{that}.options.extensionId", "{arguments}.0"], - priority: "first" - }, - "onExtUninstalled.unpopulate": { - listener: "{that}.unpopulate" - }, - "onExtEnabled.guard": { - listener: "gpii.chrome.extensionHolder.guardEventRelay", - args: ["{that}.options.extensionId", "{arguments}.0.id"], - priority: "first" - }, - "onExtDisabled.guard": { - listener: "gpii.chrome.extensionHolder.guardEventRelay", - args: ["{that}.options.extensionId", "{arguments}.0.id"], - priority: "first" - }, - "onError.translateToOnExtensionMissing": { - listener: "gpii.chrome.extensionHolder.translateToOnExtensionMissing", - args: ["{that}", "{arguments}.0"] - } - }, - modelListeners: { - extensionEnabled: { - func: "{that}.updateEnabledStatus", - excludeSource: "init" - } - } -}); - -// only relay chrome event if it is for the matching extension -gpii.chrome.extensionHolder.guardEventRelay = function (extensionId, id) { - return extensionId === id; -}; - -gpii.chrome.extensionHolder.unpopulate = function (that) { - // clear out any partial or old extension instance - that.extensionInstance = undefined; - // remove events when no extension instance present - that.events.onExtDisabled.removeListener("updateModel"); - that.events.onExtEnabled.removeListener("updateModel"); -}; - -gpii.chrome.extensionHolder.populate = function (that, useExtensionState) { - chrome.management.get(that.options.extensionId, function (extInfo) { - if (chrome.runtime.lastError) { - that.unpopulate(); - fluid.log(fluid.logLevel.FAIL, - "Could not get extensionInfo, error was:", - chrome.runtime.lastError.message); - that.events.onError.fire(chrome.runtime.lastError); - } else { - that.extensionInstance = extInfo; - // Manually binding the events because the onEnabled event is fired - // with onInstall. We want to ignore that initial onEnabled event - // otherwise the event handler will update the model. - that.events.onExtDisabled.addListener(function () { - that.updateModel(false); - }, "updateModel"); - that.events.onExtEnabled.addListener(function () { - that.updateModel(true); - }, "updateModel"); - - if (useExtensionState) { - that.updateModel(that.extensionInstance.enabled); - } else { - that.updateEnabledStatus(); - } - } - }); -}; - -gpii.chrome.extensionHolder.updateEnabledStatus = function (that) { - if (that.extensionInstance) { - that.extensionInstance.enabled = that.model.extensionEnabled; - chrome.management.setEnabled(that.extensionInstance.id, that.model.extensionEnabled, that.events.onSetEnabled.fire); - } else if (that.model.extensionEnabled) { - that.events.onExtensionMissing.fire(); - } -}; - -gpii.chrome.extensionHolder.setEnabledComplete = function (that) { - // Only fire the error if an error, it is enabled, and an extension instance exists - // This condition is needed because it will through two errors when removing an enabled extension through - // the chrome interface. It first fires a onDisabled then an onUninstalled event. - if (chrome.runtime.lastError && !that.model.extensionEnabled && !that.extensionInstance) { - fluid.log(fluid.logLevel.FAIL, - "Could not enable extension, error was:", - chrome.runtime.lastError.message); - that.events.onError.fire(chrome.runtime.lastError); - }; -}; - -gpii.chrome.extensionHolder.translateToOnExtensionMissing = function (that, error) { - if (error.message === "Failed to find extension with id " + that.options.extensionId + ".") { - that.events.onExtensionMissing.fire(); - } -}; diff --git a/extension/templates/DictionaryPanelTemplate.html b/extension/templates/DictionaryPanelTemplate.html deleted file mode 100644 index 2c9c1ca..0000000 --- a/extension/templates/DictionaryPanelTemplate.html +++ /dev/null @@ -1,10 +0,0 @@ - -