uio-plus
Version:
User Interface Options Plus (UIO+) allows you to customize websites to match your own personal needs and preferences.
429 lines (400 loc) • 15.9 kB
JavaScript
/*
* Copyright The UIO+ copyright holders
* See the AUTHORS.md file at the top-level directory of this distribution and at
* https://github.com/fluid-project/uio-plus/blob/master/AUTHORS.md
*
* Licensed under the BSD 3-Clause 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/fluid-project/uio-plus/blob/master/LICENSE.txt
*/
/* global fluid, chrome */
"use strict";
(function ($, fluid) {
var uioPlus = fluid.registerNamespace("uioPlus");
// The main component to handle settings that require DOM manipulations.
// It contains various subcomponents for handling various settings.
fluid.defaults("uioPlus.chrome.domEnactor", {
gradeNames: ["fluid.contextAware", "fluid.viewComponent"],
model: {
// Accepted model values:
// characterSpace: Number,
// contrastTheme: String,
// inputsLargerEnabled: Boolean,
// lineSpace: Number, // the multiplier to the current line space
// selectionTheme: String,
// selfVoicingEnabled: Boolean,
// simplifiedUiEnabled: Boolean,
// syllabificationEnabled: Boolean,
// tableOfContentsEnabled: Boolean
},
events: {
onIncomingSettings: null
},
listeners: {
"onIncomingSettings.updateModel": "{that}.updateModel"
},
contextAwareness: {
simplify: {
checks: {
allowSimplification: {
contextValue: "{uioPlus.chrome.allowSimplification}",
gradeNames: "uioPlus.chrome.domEnactor.simplify"
}
}
}
},
invokers: {
updateModel: {
funcName: "uioPlus.chrome.domEnactor.updateModel",
args: ["{that}", "{arguments}.0"]
}
},
distributeOptions: {
record: "{that}.container",
target: "{that > fluid.prefs.enactor}.container"
},
components: {
portBinding: {
type: "uioPlus.chrome.portBinding",
options: {
portName: "contentScript",
listeners: {
"onIncomingRead.handle": {
listener: "{that}.rejectMessage",
args: ["{that}.options.messageTypes.readReceipt", "{arguments}.0"]
}
},
invokers: {
handleRead: "fluid.identity",
handleWrite: {
func: "{domEnactor}.events.onIncomingSettings.fire",
args: ["{arguments}.0.payload.settings"]
}
}
}
},
charSpace: {
type: "uioPlus.chrome.enactor.charSpace",
options: {
model: {
value: "{domEnactor}.model.characterSpace"
}
}
},
contrast: {
type: "uioPlus.chrome.enactor.contrast",
options: {
model: {
value: "{domEnactor}.model.contrastTheme"
}
}
},
inputsLarger: {
type: "uioPlus.chrome.enactor.inputsLarger",
options: {
model: {
value: "{domEnactor}.model.inputsLargerEnabled"
}
}
},
lineSpace: {
type: "uioPlus.chrome.enactor.lineSpace",
options: {
model: {
value: "{domEnactor}.model.lineSpace"
}
}
},
selectionHighlight: {
type: "uioPlus.chrome.enactor.selectionHighlight",
options: {
model: {
value: "{domEnactor}.model.selectionTheme",
selectParagraph: "{domEnactor}.model.clickToSelectEnabled"
}
}
},
selfVoicing: {
type: "uioPlus.chrome.enactor.selfVoicing",
options: {
model: {
enabled: "{domEnactor}.model.selfVoicingEnabled"
},
// GPII-3373: temporarily remove the page level TTS until GPII-3286 is fixed
// https://issues.gpii.net/browse/GPII-3286
components: {
orator: {
options: {
components: {
controller: {
type: "fluid.emptySubcomponent"
},
domReader: {
type: "fluid.emptySubcomponent"
},
selectionReader: {
options: {
markup: {
control: "<button class=\"flc-orator-selectionReader-control uioPlusJS-simplify-visible \"><span class=\"uioPlus-icon-orator\"></span><span class=\"flc-orator-selectionReader-controlLabel\"></span></button>"
}
}
}
}
}
}
}
}
},
syllabification: {
type: "uioPlus.chrome.enactor.syllabification",
options: {
model: {
enabled: "{domEnactor}.model.syllabificationEnabled"
}
}
},
tableOfContents: {
type: "uioPlus.chrome.enactor.tableOfContents",
options: {
model: {
toc: "{domEnactor}.model.tableOfContentsEnabled"
}
}
},
wordSpace: {
type: "uioPlus.chrome.enactor.wordSpace",
options: {
model: {
value: "{domEnactor}.model.wordSpace"
}
}
}
}
});
uioPlus.chrome.domEnactor.updateModel = function (that, model) {
var transaction = that.applier.initiate();
transaction.fireChangeRequest({path: "", type: "DELETE"});
transaction.change("", model);
transaction.commit();
};
fluid.defaults("uioPlus.chrome.domEnactor.simplify", {
components: {
simplify: {
type: "uioPlus.chrome.enactor.simplify",
options: {
model: {
simplify: "{domEnactor}.model.simplifiedUiEnabled"
}
}
}
}
});
// High contrast
fluid.defaults("uioPlus.chrome.enactor.contrast", {
gradeNames: ["fluid.prefs.enactor.contrast"],
classes: {
"default": "",
"bw": "fl-theme-uioPlus-bw",
"wb": "fl-theme-uioPlus-wb",
"by": "fl-theme-uioPlus-by",
"yb": "fl-theme-uioPlus-yb",
"gd": "fl-theme-uioPlus-gd",
"gw": "fl-theme-uioPlus-gw",
"bbr": "fl-theme-uioPlus-bbr"
}
});
// fontsize map
fluid.defaults("uioPlus.chrome.enactor.fontSizeMap", {
fontSizeMap: {
"xx-small": "9px",
"x-small": "11px",
"small": "13px",
"medium": "15px",
"large": "18px",
"x-large": "23px",
"xx-large": "30px"
}
});
// Character space
fluid.defaults("uioPlus.chrome.enactor.charSpace", {
gradeNames: ["uioPlus.chrome.enactor.fontSizeMap", "fluid.prefs.enactor.letterSpace"]
});
// Line space
fluid.defaults("uioPlus.chrome.enactor.lineSpace", {
gradeNames: ["uioPlus.chrome.enactor.fontSizeMap", "fluid.prefs.enactor.lineSpace"]
});
// Word space
fluid.defaults("uioPlus.chrome.enactor.wordSpace", {
gradeNames: ["uioPlus.chrome.enactor.fontSizeMap", "fluid.prefs.enactor.wordSpace"]
});
// Inputs larger
fluid.defaults("uioPlus.chrome.enactor.inputsLarger", {
gradeNames: ["fluid.prefs.enactor.enhanceInputs"],
cssClass: "uioPlus-input-enhanced"
});
// Selection highlight
fluid.defaults("uioPlus.chrome.enactor.selectionHighlight", {
gradeNames: ["fluid.prefs.enactor.classSwapper"],
classes: {
"default": "",
"yellow": "uioPlus-selection-yellow",
"green": "uioPlus-selection-green",
"pink": "uioPlus-selection-pink"
},
listeners: {
"onCreate.rightClick": {
"this": "{that}.container",
method: "contextmenu",
args: ["{that}.handleRightClick"]
}
},
invokers: {
selectParagraph: "uioPlus.chrome.enactor.selectionHighlight.selectParagraph",
handleRightClick: {
funcName: "uioPlus.chrome.enactor.selectionHighlight.handleRightClick",
args: ["{that}.model", "{arguments}.0", "{that}.selectParagraph"]
}
}
});
uioPlus.chrome.enactor.selectionHighlight.selectParagraph = function (node) {
// find closest paragraph node
node = $(node);
var paragraphNode = node.closest("p")[0] || node[0];
if (paragraphNode) {
// create a range containg the paragraphNode
var range = new Range();
range.selectNode(paragraphNode);
// retrieve the current selection
var selection = window.getSelection();
// clear all ranges in the selection
selection.removeAllRanges();
// add the new range based on the RIGHT-ClICKed paragraph
selection.addRange(range);
}
};
uioPlus.chrome.enactor.selectionHighlight.handleRightClick = function (model, event, handler) {
// Check if the right mouse button was pressed so that this isn't
// triggered by the context menu key ( https://api.jquery.com/contextmenu/ ).
// Only trigger the handler if the appropriate model condition is met.
if (event.button === 2 && model.selectParagraph) {
handler(event.target);
event.preventDefault();
}
};
// Simplification
fluid.defaults("uioPlus.chrome.enactor.simplify", {
gradeNames: ["fluid.prefs.enactor", "uioPlus.simplify"],
injectNavToggle: false
});
// Syllabification
fluid.defaults("uioPlus.chrome.enactor.syllabification", {
gradeNames: ["fluid.prefs.enactor.syllabification"],
terms: {
patternPrefix: "js/lib/syllablePatterns"
},
markup: {
separator: "<span class=\"flc-syllabification-separator uioPlus-syllabification-separator\"></span>"
},
invokers: {
injectScript: "uioPlus.chrome.enactor.syllabification.injectScript"
}
});
uioPlus.chrome.enactor.syllabification.injectScript = function (src) {
var promise = fluid.promise();
chrome.runtime.sendMessage({
type: "uioPlus.chrome.contentScriptInjectionRequest",
src: src
}, promise.resolve);
return promise;
};
// Table of contents
fluid.defaults("uioPlus.chrome.enactor.tableOfContents", {
gradeNames: ["uioPlus.chrome.contentView", "fluid.prefs.enactor.tableOfContents"],
tocTemplate: {
// Converts the relative path to a fully-qualified URL in the extension.
expander: {
funcName: "chrome.runtime.getURL",
args: ["templates/TableOfContents.html"]
}
},
tocMessage: {
// Converts the relative path to a fully-qualified URL in the extension.
expander: {
funcName: "chrome.runtime.getURL",
args: ["messages/tableOfContents-enactor.json"]
}
},
selectors: {
tocContainer: ".flc-toc-tocContainer",
article: "article, [role~='article'], .article, #article",
main: "main, [role~='main'], .main, #main",
genericContent: ".content, #content, .body:not('body'), #body:not('body')"
},
defaultContent: "{that}.container",
// Handle the initial model value when the component creation cycle completes instead of
// relying on model listeners. See https://issues.fluidproject.org/browse/FLUID-5519
listeners: {
"onCreate.handleInitialModelValue": {
listener: "{that}.applyToc",
args: ["{that}.model.toc"]
},
"onCreateTOCReady.injectToCContainer": {
listener: "{that}.injectToCContainer",
priority: "first"
}
},
invokers: {
injectToCContainer: {
funcName: "uioPlus.chrome.enactor.tableOfContents.injectToCContainer",
args: ["{that}"]
}
},
markup: {
tocContainer: "<div class=\"flc-toc-tocContainer uioPlus-toc-tocContainer\"></div>"
},
distributeOptions: {
source: "{that}.options.selectors.tocContainer",
target: "{that tableOfContents}.options.selectors.tocContainer"
}
});
uioPlus.chrome.enactor.tableOfContents.injectToCContainer = function (that) {
if (!that.locate("tocContainer").length) {
if (that.content.length === 1) {
that.content.prepend(that.options.markup.tocContainer);
} else {
that.container.prepend(that.options.markup.tocContainer);
}
}
};
// Self Voicing
fluid.defaults("uioPlus.chrome.enactor.selfVoicing", {
gradeNames: ["uioPlus.chrome.contentView", "fluid.prefs.enactor.selfVoicing"],
selectors: {
controllerParentContainer: ".flc-prefs-selfVoicingWidget",
domReaderContent: ".flc-orator-content"
},
domReaderContent: ["domReaderContent", "article", "main", "genericContent"],
controllerParentContainer: ["controllerParentContainer", "article", "main", "genericContent"],
distributeOptions: [{
record: {
expander: {
funcName: "uioPlus.chrome.contentView.findFirstSelector",
args: ["{selfVoicing}.locate", "{selfVoicing}.options.controllerParentContainer", "{selfVoicing}.container"]
}
},
target: "{that orator > controller}.options.parentContainer",
namespace: "controllerParentContainer"
}, {
record: {
expander: {
funcName: "uioPlus.chrome.contentView.findFirstSelector",
args: ["{selfVoicing}.locate", "{selfVoicing}.options.domReaderContent", "{selfVoicing}.container"]
}
},
target: "{that orator}.options.components.domReader.container",
namespace: "domReaderContainer"
}]
});
})(jQuery, fluid);