Merge branch 'master' into i236-common-ex-aliases

This commit is contained in:
Isaac Khor 2017-12-31 16:57:57 +08:00
commit 2441fba8c0
17 changed files with 269 additions and 72 deletions

View file

@ -76,7 +76,7 @@ NOTE: key modifiers (eg: control, alt) are not supported yet. See the FAQ below.
- Why doesn't Tridactyl work on websites with frames?
It should work on some frames now. See [#122](https://github.com/cmcaine/tridactyl/issues/122).
   It should work on some frames now. See [#122](https://github.com/cmcaine/tridactyl/issues/122).
- Can I change proxy via commands?
@ -92,7 +92,7 @@ NOTE: key modifiers (eg: control, alt) are not supported yet. See the FAQ below.
- Why doesn't Tridactyl work on some pages?
One possible reason is that the site has a strict content security policy. We can rewrite these to make Tridactyl work, but we do not want to worsen the security of sensitive pages, so it is taking us a little while. See #112.
   One possible reason is that the site has a strict content security policy. We can rewrite these to make Tridactyl work, but we do not want to worsen the security of sensitive pages, so it is taking us a little while. See [#112](https://github.com/cmcaine/tridactyl/issues/112).
- How can I know which mode I'm in/have a status line?

View file

@ -1,5 +1,8 @@
/** Inject an input element into unsuspecting webpages and provide an API for interaction with tridactyl */
import Logger from './logging'
const logger = new Logger('messaging')
/* TODO:
CSS
Friendliest-to-webpage way of injecting commandline bar?
@ -16,14 +19,13 @@ let cmdline_iframe: HTMLIFrameElement = undefined
function init(){
if (cmdline_iframe === undefined && window.document.body !== null) {
try {
console.log("INIT")
cmdline_iframe = window.document.createElement("iframe")
cmdline_iframe.setAttribute("src", browser.extension.getURL("static/commandline.html"))
cmdline_iframe.setAttribute("id", "cmdline_iframe")
hide()
window.document.body.appendChild(cmdline_iframe)
} catch (e) {
console.error("Couldn't initialise cmdline_iframe!", e)
logger.error("Couldn't initialise cmdline_iframe!", e)
}
}
}

View file

@ -8,6 +8,8 @@ import * as SELF from './commandline_frame'
import './number.clamp'
import state from './state'
import * as Config from './config'
import Logger from './logging'
const logger = new Logger('cmdline')
let activeCompletions: Completions.CompletionSource[] = undefined
let completionsDiv = window.document.getElementById("completions") as HTMLElement
@ -161,7 +163,7 @@ clInput.addEventListener("input", () => {
}
// Fire each completion and add a callback to resize area
console.log(activeCompletions)
logger.debug(activeCompletions)
activeCompletions.forEach(comp =>
comp.filter(newCmd).then(() => resizeArea())
)
@ -212,9 +214,7 @@ function history(n){
/* Send the commandline to the background script and await response. */
function process() {
console.log(clInput.value)
const command = getCompletion() || clInput.value
console.log(command)
hide_and_clear()
@ -225,7 +225,6 @@ function process() {
) {
state.cmdHistory = state.cmdHistory.concat([command])
}
console.log(state.cmdHistory)
cmdline_history_position = 0
sendExstr(command)
@ -263,7 +262,7 @@ export function setClipboard(content: string) {
scratchpad.select()
if (document.execCommand("Copy")) {
// // todo: Maybe we can consider to using some logger and show it with status bar in the future
console.log('set clipboard:', scratchpad.value)
logger.info('set clipboard:', scratchpad.value)
} else throw "Failed to copy!"
})
}
@ -272,7 +271,6 @@ export function getClipboard() {
return applyWithTmpTextArea(scratchpad => {
scratchpad.focus()
document.execCommand("Paste")
console.log('get clipboard', scratchpad.textContent)
return scratchpad.textContent
})
}

View file

@ -622,17 +622,21 @@ export class BufferCompletionSource extends CompletionSourceFuse {
super.setStateFromScore(scoredOpts, true)
}
/** Score with fuse unless query is an integer or a single # */
/** Score with fuse unless query is a single # or looks like a buffer index */
scoredOptions(query: string, options = this.options): ScoredOption[] {
const args = query.split(/\s+/gu)
if (args.length <= 2) {
const args = query.trim().split(/\s+/gu)
if (args.length === 1) {
// if query is an integer n and |n| < options.length
if (Number.isInteger(Number(args[0]))) {
const index = (Number(args[0]) - 1).mod(options.length)
return [{
index,
option: options[index],
score: 0,
}]
let index = Number(args[0]) - 1
if (Math.abs(index) < options.length) {
index = index.mod(options.length)
return [{
index,
option: options[index],
score: 0,
}]
}
} else if (args[0] === '#') {
for (const [index, option] of enumerate(options)) {
if (option.isAlternative) {

View file

@ -8,6 +8,7 @@
//
// Really, we'd like a way of just letting things use the variables
//
const CONFIGNAME = "userconfig"
type StorageMap = browser.storage.StorageMap
@ -147,6 +148,16 @@ const DEFAULTS = o({
"ttsrate": 1, // 0.1 to 10
"ttspitch": 1, // 0 to 2
"vimium-gi": true,
// Default logging levels - 2 === WARNING
"logging": o({
"messaging": 2,
"cmdline": 2,
"controller": 2,
"hinting": 2,
"state": 2,
"excmd": 1,
}),
})
// currently only supports 2D or 1D storage

View file

@ -3,6 +3,7 @@ import {isTextEditable} from './dom'
import {isSimpleKey} from './keyseq'
import state from "./state"
import {repeat} from './excmds_background'
import Logger from "./logging"
import {parser as exmode_parser} from './parsers/exmode'
import {parser as hintmode_parser} from './hinting_background'
@ -13,6 +14,7 @@ import * as gobblemode from './parsers/gobblemode'
import * as inputmode from './parsers/inputmode'
const logger = new Logger('controller')
/** Accepts keyevents, resolves them to maps, maps to exstrs, executes exstrs */
function *ParserController () {
@ -41,7 +43,7 @@ function *ParserController () {
state.mode = "normal"
}
}
console.log(keyevent, state.mode)
logger.debug(keyevent, state.mode)
// Special keys (e.g. Backspace) are not handled properly
// yet. So drop them. This also drops all modifier keys.
@ -61,7 +63,7 @@ function *ParserController () {
response = (parsers[state.mode] as any)([keyevent])
break
}
console.debug(keys, response)
logger.debug(keys, response)
if (response.ex_str){
ex_str = response.ex_str

View file

@ -66,13 +66,9 @@ export async function downloadUrl(url: string, saveAs: boolean) {
// TODO: at this point, could give feeback using the promise returned
// by downloads.download(), needs status bar to show it (#90)
downloadPromise.then(id => {
//console.log("Downloaded OK: ", urlToSave)
},
error => {
console.log("Failed to download: ", urlToDownload, error)
}
)
// By awaiting the promise, we ensure that if it errors, the error will be
// thrown by this function too.
await downloadPromise
}
import * as Messaging from "./messaging"

View file

@ -95,6 +95,8 @@ import * as CommandLineBackground from './commandline_background'
import * as DOM from './dom'
import * as config from './config'
import * as Logging from "./logging"
const logger = new Logging.Logger('excmds')
/** @hidden */
@ -115,18 +117,17 @@ function hasScheme(uri: string) {
/** @hidden */
function searchURL(provider: string, query: string) {
if (provider == "search") provider = config.get("searchengine")
let searchurlprovider = config.get("searchurls", provider)
if (searchurlprovider !== undefined){
const url = new URL(searchurlprovider + encodeURIComponent(query))
// URL constructor doesn't convert +s because they're valid literals in
// the standard it adheres to. But they are special characters in
// x-www-form-urlencoded and e.g. google excepts query parameters in
// that format.
url.search = url.search.replace(/\+/g, '%2B')
return url
} else {
const searchurlprovider = config.get("searchurls", provider)
if (searchurlprovider === undefined){
throw new TypeError(`Unknown provider: '${provider}'`)
}
// build search URL: either replace "%s" in URL with query or append query to URL
const url = searchurlprovider.includes("%s") ?
new URL(searchurlprovider.replace("%s", encodeURIComponent(query))) :
new URL(searchurlprovider + encodeURIComponent(query))
return url
}
/** If maybeURI doesn't have a schema, affix http:// */
@ -145,7 +146,6 @@ function forceURI(maybeURI: string): string {
const args = maybeURI.split(' ')
return searchURL(args[0], args.slice(1).join(' ')).href
} catch (e) {
console.log(e)
if (e.name !== 'TypeError') throw e
}
@ -172,6 +172,33 @@ function tabSetActive(id: number) {
// }}}
// {{{ INTERNAL/DEBUG
/**
* Set the logging level for a given logging module.
*
* @param logModule the logging module to set the level on
* @param level the level to log at: in increasing verbosity, one of
* "never", "error", "warning", "info", "debug"
*/
//#background
export function loggingsetlevel(logModule: string, level: string) {
const map = {
"never": Logging.LEVEL.NEVER,
"error": Logging.LEVEL.ERROR,
"warning": Logging.LEVEL.WARNING,
"info": Logging.LEVEL.INFO,
"debug": Logging.LEVEL.DEBUG,
}
let newLevel = map[level.toLowerCase()]
if (newLevel !== undefined) {
config.set("logging", newLevel, logModule)
}
}
// }}}
// {{{ PAGE CONTEXT
@ -270,7 +297,6 @@ export async function reloadhard(n = 1) {
//#content
export function open(...urlarr: string[]) {
let url = urlarr.join(" ")
console.log("open url:" + url)
window.location.href = forceURI(url)
}
@ -284,7 +310,6 @@ export function open(...urlarr: string[]) {
//#background
export function home(all: "false" | "true" = "false"){
let homepages = config.get("homepages")
console.log(homepages)
if (homepages.length > 0){
if (all === "false") open(homepages[homepages.length - 1])
else {
@ -904,7 +929,7 @@ export function repeat(n = 1, ...exstr: string[]) {
let cmd = state.last_ex_str
if (exstr.length > 0)
cmd = exstr.join(" ")
console.log("repeating " + cmd + " " + n + " times")
logger.debug("repeating " + cmd + " " + n + " times")
for (let i = 0; i < n; i++)
controller.acceptExCmd(cmd)
}
@ -1177,12 +1202,10 @@ export async function sanitize(...args: string[]) {
}
since = { "since": (new Date()).getTime() - millis }
} else {
console.log(":sanitize error: expected time format: ^([0-9])+(m|h|d|w)$, given format:" + args[flagpos+1])
return
throw new Error(":sanitize error: expected time format: ^([0-9])+(m|h|d|w)$, given format:" + args[flagpos+1])
}
} else {
console.log(":sanitize error: -t given but no following arguments")
return
throw new Error(":sanitize error: -t given but no following arguments")
}
}
@ -1418,10 +1441,10 @@ export async function ttsread(mode: "-t" | "-c", ...args: string[]) {
if (args.length > 0) {
tssReadFromCss(args[0])
} else {
console.log("Error: no CSS selector supplied")
throw "Error: no CSS selector supplied"
}
} else {
console.log("Unknown mode for ttsread command: " + mode)
throw "Unknown mode for ttsread command: " + mode
}
}
@ -1460,7 +1483,7 @@ export async function ttscontrol(action: string) {
if (ttsAction) {
TTS.doAction(ttsAction)
} else {
console.log("Unknown text-to-speech action: " + action)
throw new Error("Unknown text-to-speech action: " + action)
}
}

View file

@ -19,6 +19,8 @@ import {messageActiveTab, message} from './messaging'
import * as config from './config'
import * as TTS from './text_to_speech'
import {HintSaveType} from './hinting_background'
import Logger from './logging'
const logger = new Logger('hinting')
/** Simple container for the state of a single frame's hints. */
class HintState {
@ -53,13 +55,13 @@ export function hintPage(
state.mode = 'hint'
modeState = new HintState()
for (let [el, name] of izip( hintableElements, names)) {
console.log({el, name})
logger.debug({el, name})
modeState.hintchars += name
modeState.hints.push(new Hint(el, name, onSelect))
}
if (modeState.hints.length) {
console.log("HINTS", modeState.hints)
logger.debug("hints", modeState.hints)
modeState.focusedHint = modeState.hints[0]
modeState.focusedHint.focused = true
document.body.appendChild(modeState.hintHost)
@ -455,7 +457,7 @@ function hintSave(hintType: HintSaveType, saveAs: boolean) {
}
function selectFocusedHint() {
console.log("Selecting hint.", state.mode)
logger.debug("Selecting hint.", state.mode)
const focused = modeState.focusedHint
reset()
focused.select()

View file

@ -70,7 +70,6 @@ import {MsgSafeKeyboardEvent} from './msgsafe'
*/
export function parser(keys: MsgSafeKeyboardEvent[]) {
console.log("hintparser", keys)
const key = keys[0].key
if (key === 'Escape') {
reset()

72
src/logging.ts Normal file
View file

@ -0,0 +1,72 @@
/**
* Helper functions for logging
*/
import * as Config from "./config"
export enum LEVEL {
NEVER = 0, // don't use this in calls to log()
ERROR = 1,
WARNING = 2,
INFO = 3,
DEBUG = 4,
}
export class Logger {
/**
* Config-aware Logger class.
*
* @param logModule the logging module name: this is ued to look up the
* configured/default level in the user config
*/
constructor(private logModule) {}
/**
* Config-aware logging function.
*
* @param level the level of the logging - if <= configured, the message
* will be shown
*
* @return logging function: this is returned as a function to
* retain the call site
*/
private log(level: LEVEL) {
let configedLevel = Config.get("logging", this.logModule) || LEVEL.WARNING
if (level <= configedLevel) {
// hand over to console.log, error or debug as needed
switch (level) {
case LEVEL.ERROR:
return console.error
case LEVEL.WARNING:
return console.warn
case LEVEL.INFO:
return console.log
case LEVEL.DEBUG:
return console.debug
}
}
// do nothing with the message
return function(...args) {}
}
// These are all getters so that logger.debug = console.debug and
// logger.debug('blah') translates into console.debug('blah') with the
// filename and line correct.
public get debug() {
return this.log(LEVEL.DEBUG)
}
public get info() {
return this.log(LEVEL.INFO)
}
public get warning() {
return this.log(LEVEL.WARNING)
}
public get error() {
return this.log(LEVEL.ERROR)
}
}
export default Logger

View file

@ -1,4 +1,6 @@
import {l, browserBg, activeTabId} from './lib/webext'
import Logger from './logging'
const logger = new Logger('messaging')
export type TabMessageType =
"excmd_content" |
@ -24,7 +26,8 @@ export type listener = (message: Message, sender?, sendResponse?) => void|Promis
// Calls methods on obj that match .command and sends responses back
export function attributeCaller(obj) {
function handler(message: Message, sender, sendResponse) {
console.log("Message:", message)
logger.debug(message)
// Args may be undefined, but you can't spread undefined...
if (message.args === undefined) message.args = []
@ -35,17 +38,17 @@ export function attributeCaller(obj) {
// Return response to sender
if (response instanceof Promise) {
console.log("Returning promise...", response)
logger.debug("Returning promise...", response)
sendResponse(response)
// Docs say you should be able to return a promise, but that
// doesn't work.
/* return response */
} else if (response !== undefined) {
console.log("Returning synchronously...", response)
logger.debug("Returning synchronously...", response)
sendResponse(response)
}
} catch (e) {
console.error(`Error processing ${message.command}(${message.args})`, e)
logger.error(`Error processing ${message.command}(${message.args})`, e)
return new Promise((resolve, error)=>error(e))
}
}
@ -78,7 +81,7 @@ export async function messageAllTabs(type: TabMessageType, command: string, args
let responses = []
for (let tab of await browserBg.tabs.query({})) {
try { responses.push(await messageTab(tab.id, type, command, args)) }
catch (e) { console.error(e) }
catch (e) { logger.error(e) }
}
return responses
}

View file

@ -14,6 +14,9 @@
If this turns out to be expensive there are improvements available.
*/
import Logger from './logging'
const logger = new Logger('state')
export type ModeName = 'normal' | 'insert' | 'hint' | 'ignore' | 'gobble' | 'input'
class State {
mode: ModeName = 'normal'
@ -27,10 +30,10 @@ const defaults = Object.freeze(new State())
const overlay = {} as any
browser.storage.local.get('state').then(res=>{
if ('state' in res) {
console.log("Loaded initial state:", res.state)
logger.debug("Loaded initial state:", res.state)
Object.assign(overlay, res.state)
}
}).catch(console.error)
}).catch((...args) => logger.error(...args))
const state = new Proxy(overlay, {
@ -45,7 +48,7 @@ const state = new Proxy(overlay, {
/** Persist sets to storage immediately */
set: function(target, property, value) {
console.log("State changed!", property, value)
logger.debug("State changed!", property, value)
target[property] = value
browser.storage.local.set({state: target})
return true

View file

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48mm"
height="48mm"
viewBox="0 0 48 48"
version="1.1"
id="svg8"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
sodipodi:docname="tridactyl.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.2879385"
inkscape:cx="99.585653"
inkscape:cy="71.989006"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1340"
inkscape:window-height="915"
inkscape:window-x="0"
inkscape:window-y="144"
inkscape:window-maximized="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-249)">
<circle
style="opacity:1;fill:#1f9947;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path5917"
cx="24"
cy="273"
r="19" />
<path
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 33.14566,271.14395 c 2.905194,-2.00919 3.118816,-2.94966 3.631495,-3.67638 0.512688,-0.72674 1.196262,0.17099 1.196262,0.94047 0,0.76946 -0.640857,5.38632 -1.196262,5.89931 -0.555405,0.51298 -1.324423,0.68397 -1.324423,0.68397 l -3.076099,-3.1634 z"
id="path5923"
inkscape:connector-curvature="0" />
<path
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 14.032233,262.59755 c -0.755255,-3.83894 -0.725044,-4.53419 -0.513574,-5.22942 0.21147,-0.69524 -0.573988,-1.6323 -1.208407,-0.87661 -0.634409,0.7557 -3.413739,5.01783 -2.869963,5.53169 0.543785,0.51388 -0.120837,1.17889 0.936513,1.17889 1.057359,0 3.655431,-0.48365 3.655431,-0.60455 z"
id="path5925"
inkscape:connector-curvature="0" />
<path
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 24.498991,256.83184 c 0.138757,-1.35684 6.121005,-5.9549 6.566457,-5.79613 0.44545,0.15876 -0.0039,7.94853 -0.473385,8.34084 -0.469496,0.39233 -6.093072,-2.54471 -6.093072,-2.54471 z"
id="path5921"
inkscape:connector-curvature="0" />
<path
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 13.993892,293.90696 c 5.169539,2.7359 5.169548,-1.28246 8.331091,-2.13744 3.161542,-0.85497 5.425887,-0.76947 7.134828,-4.78784 1.708941,-4.01837 4.443248,-3.1634 5.554066,-4.70235 1.11081,-1.53895 4.051903,-7.19695 0.512679,-9.74668 -1.716916,-1.23691 -3.872923,-2.11206 -5.725261,-0.61954 -1.604661,1.29296 -2.768054,4.49046 -4.870175,7.28832 0.85447,-6.88253 4.497485,-9.76253 5.981292,-13.25208 1.100194,-2.58737 0.299066,-6.28405 -2.69158,-7.99399 -4.150603,-2.37316 -8.175477,0.98531 -8.758326,4.10386 -1.235154,6.60881 -0.939923,10.72992 -3.076099,14.57729 -0.411817,-6.03522 2.459883,-7.09235 0.512688,-11.45663 -1.068093,-2.39392 -3.845126,-3.59088 -6.237641,-2.65042 -2.3925238,0.94048 -3.7169555,4.40312 -3.5460597,7.13903 0.1708959,2.73591 2.1592337,5.05118 1.0680925,8.42148 -0.98264,3.03515 0,4.44585 1.9652792,8.03674 1.965289,3.59088 -0.683574,4.53136 3.845126,7.78025 z"
id="path5919"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccsssscssscsccssc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -24,8 +24,7 @@ export function readText(text: string): void {
if (window.speechSynthesis.getVoices().length === 0) {
// should try to warn user? This apparently can happen on some machines
// TODO: Implement when there's an error feedback mechanism
console.log("No voice found: cannot use Text-To-Speech API")
return
throw new Error("No voice found: cannot use Text-To-Speech API")
}
let utterance = new SpeechSynthesisUtterance(text);

View file

@ -61,17 +61,19 @@ function test_parent() {
// single level path
["http://example.com/path", "http://example.com/"],
// multi-level path
["http://example.com/path1/path2", "http://example.com/path1"],
["http://example.com/path1/path2", "http://example.com/path1/"],
["http://example.com/path1/path2/path3", "http://example.com/path1/path2/"],
// subdomains
["http://sub.example.com", "http://example.com/"],
// subdom with path, leave subdom
["http://sub.example.com/path", "http://sub.example.com/"],
// trailing slash
["http://sub.example.com/path/", "http://sub.example.com/"],
["http://sub.example.com/path/to/", "http://sub.example.com/path/"],
// repeated slash
["http://example.com/path//", "http://example.com/"],
// repeated slash
["http://example.com//path//", "http://example.com/"],
["http://example.com//path//", "http://example.com//"],
["http://example.com//path//", "http://example.com//"],
]
for (let [url, exp_parent] of cases) {

View file

@ -78,13 +78,10 @@ export function getUrlParent(url, count = 1) {
return gup(parent, count - 1)
}
// pathname always starts '/'
// empty path is '/'
if (parent.pathname !== '/') {
// Split on '/' and remove empty substrings
// (handles initial and trailing slashes, repeated slashes, etc.)
let path = parent.pathname.split('/').filter(sub => sub !== "")
path.pop()
parent.pathname = path.join('/')
// Remove trailing slashes and everything to the next slash:
parent.pathname = parent.pathname.replace(/\/[^\/]*?\/*$/, '/')
return gup(parent, count - 1)
}