mirror of
https://github.com/vale981/tridactyl
synced 2025-03-06 01:51:40 -05:00
Merge branch 'master' of github.com:cmcaine/tridactyl into glacambre-impl_bufferall
This commit is contained in:
commit
be5c4e2d75
19 changed files with 590 additions and 36 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -1,5 +1,25 @@
|
|||
# Tridactyl changelog
|
||||
|
||||
## Release 1.13.1 // Unreleased
|
||||
|
||||
* New features
|
||||
|
||||
* Container management with `container{create,close,update,delete}` and `tabopen -c [container name] URL`
|
||||
* see `help containercreate` for more information
|
||||
* Mode indicator's border now uses the current container colour
|
||||
* `set hintnames numeric` for sequential numeric hints. Best used with `set hintfiltermode vimperator-reflow`.
|
||||
* Changelog now tells you when there's a new changelog that you haven't read.
|
||||
|
||||
* Bug fixes
|
||||
|
||||
* `nativeopen` now puts tabs in the same place that `tabopen` would
|
||||
* `santise tridactyllocal tridactylsync` now works in RC files
|
||||
* Missing ;w hint winopen bind added
|
||||
* Fixed minor error with themes not being properly applied on some sites
|
||||
* Fixed reload bug on :help when there's no hash
|
||||
* `<C-i>` editor will now always update the field you started in, not just the one you currently have focused.
|
||||
* "email" input elements can now be focused without errors.
|
||||
|
||||
## Release 1.13.0 / 2018-06-08
|
||||
|
||||
* **Potentially breaking changes**
|
||||
|
|
|
@ -14,10 +14,11 @@ sed "1,/REPLACETHIS/ d" newtab.template.html >> "$newtabtemp"
|
|||
# Why think when you can pattern match?
|
||||
|
||||
sed "/REPLACE_ME_WITH_THE_CHANGE_LOG_USING_SED/,$ d" "$newtabtemp" > "$newtab"
|
||||
# Note: If you're going to change this HTML, make sure you don't break the JS in src/newtab.ts
|
||||
echo """
|
||||
<input type="checkbox" id="spoilerbutton" />
|
||||
<label for="spoilerbutton" onclick="">Changelog</label>
|
||||
<div class="spoiler">
|
||||
<label for="spoilerbutton" onclick=""><div id="nagbar-changelog">New features!</div>Changelog</label>
|
||||
<div id="changelog" class="spoiler">
|
||||
""" >> "$newtab"
|
||||
$(npm bin)/marked ../../CHANGELOG.md >> "$newtab"
|
||||
echo """
|
||||
|
|
|
@ -123,6 +123,7 @@ const DEFAULTS = o({
|
|||
";;": "hint -;",
|
||||
";#": "hint -#",
|
||||
";v": "hint -W exclaim_quiet mpv",
|
||||
";w": "hint -w",
|
||||
"<S-Insert>": "mode ignore",
|
||||
"<CA-Esc>": "mode ignore",
|
||||
"<CA-`>": "mode ignore",
|
||||
|
@ -141,6 +142,9 @@ const DEFAULTS = o({
|
|||
DocStart: o({
|
||||
// "addons.mozilla.org": "mode ignore",
|
||||
}),
|
||||
DocEnd: o({
|
||||
// "emacs.org": "sanitise history",
|
||||
}),
|
||||
TriStart: o({
|
||||
".*": "source_quiet",
|
||||
}),
|
||||
|
@ -232,6 +236,7 @@ const DEFAULTS = o({
|
|||
homepages: [],
|
||||
hintchars: "hjklasdfgyuiopqwertnmzxcvb",
|
||||
hintfiltermode: "simple", // "simple", "vimperator", "vimperator-reflow"
|
||||
hintnames: "short",
|
||||
|
||||
// Controls whether the page can focus elements for you via js
|
||||
// Remember to also change browser.autofocus (autofocusing elements via
|
||||
|
@ -270,6 +275,7 @@ const DEFAULTS = o({
|
|||
messaging: 2,
|
||||
cmdline: 2,
|
||||
controller: 2,
|
||||
containers: 2,
|
||||
hinting: 2,
|
||||
state: 2,
|
||||
excmd: 1,
|
||||
|
@ -296,6 +302,9 @@ const DEFAULTS = o({
|
|||
// If enabled, tabopen opens a new tab in the currently active tab's container.
|
||||
tabopencontaineraware: "false",
|
||||
|
||||
// If moodeindicator is enabled, containerindicator will color the border of the mode indicator with the container color.
|
||||
containerindicator: "true",
|
||||
|
||||
// Performance related settings
|
||||
|
||||
// number of most recent results to ask Firefox for. We display the top 20 or so most frequently visited ones.
|
||||
|
@ -473,9 +482,15 @@ async function init() {
|
|||
|
||||
// Listen for changes to the storage and update the USERCONFIG if appropriate.
|
||||
// TODO: BUG! Sync and local storage are merged at startup, but not by this thing.
|
||||
browser.storage.onChanged.addListener((changes, areaname) => {
|
||||
browser.storage.onChanged.addListener(async (changes, areaname) => {
|
||||
if (CONFIGNAME in changes) {
|
||||
USERCONFIG = changes[CONFIGNAME].newValue
|
||||
// newValue is undefined when calling browser.storage.AREANAME.clear()
|
||||
if (changes[CONFIGNAME].newValue !== undefined) {
|
||||
USERCONFIG = changes[CONFIGNAME].newValue
|
||||
} else if (areaname === (await get("storageloc"))) {
|
||||
// If newValue is undefined and AREANAME is the same value as STORAGELOC, the user wants to clean their config
|
||||
USERCONFIG = o({})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -83,6 +83,9 @@ if (
|
|||
config.getAsync("modeindicator").then(mode => {
|
||||
if (mode !== "true") return
|
||||
|
||||
// Do we want container indicators?
|
||||
let containerIndicator = config.get("containerindicator")
|
||||
|
||||
// Hide indicator in print mode
|
||||
// CSS not explicitly added to the dom doesn't make it to print mode:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1448507
|
||||
|
@ -100,6 +103,22 @@ config.getAsync("modeindicator").then(mode => {
|
|||
: ""
|
||||
statusIndicator.className =
|
||||
"cleanslate TridactylStatusIndicator " + privateMode
|
||||
|
||||
// Dynamically sets the border container color.
|
||||
if (containerIndicator === "true") {
|
||||
webext
|
||||
.activeTabContainer()
|
||||
.then(container => {
|
||||
statusIndicator.setAttribute(
|
||||
"style",
|
||||
`border: ${container.colorCode} solid 1.5px !important`,
|
||||
)
|
||||
})
|
||||
.catch(error => {
|
||||
logger.debug(error)
|
||||
})
|
||||
}
|
||||
|
||||
// This listener makes the modeindicator disappear when the mouse goes over it
|
||||
statusIndicator.addEventListener("mouseenter", ev => {
|
||||
let target = ev.target as any
|
||||
|
|
|
@ -79,6 +79,22 @@ export const potentialRules = {
|
|||
show: ``,
|
||||
},
|
||||
},
|
||||
// All children except add-on panels
|
||||
navbarnonaddonchildren: {
|
||||
name: `:root:not([customizing]) #nav-bar > :not(#customizationui-widget-panel)`,
|
||||
options: {
|
||||
hide: `display: none !important;`,
|
||||
show: ``,
|
||||
},
|
||||
},
|
||||
// Set navbar height to 0
|
||||
navbarnoheight: {
|
||||
name: `:root:not([customizing]) #nav-bar`,
|
||||
options: {
|
||||
hide: ``,
|
||||
show: `max-height: 0; min-height: 0 !important;`,
|
||||
},
|
||||
},
|
||||
// This inherits transparency if we aren't careful
|
||||
menubar: {
|
||||
name: `#navigator-toolbox:not(:hover):not(:focus-within) #toolbar-menubar > *`,
|
||||
|
@ -155,11 +171,22 @@ export const metaRules = {
|
|||
navbarunfocused: "hide",
|
||||
navtoolboxunfocused: "hide",
|
||||
navbarafter: "hide",
|
||||
navbarnonaddonchildren: "show",
|
||||
navbarnoheight: "hide",
|
||||
},
|
||||
always: {
|
||||
navbarunfocused: "show",
|
||||
navtoolboxunfocused: "show",
|
||||
navbarafter: "show",
|
||||
navbarnonaddonchildren: "show",
|
||||
navbarnoheight: "hide",
|
||||
},
|
||||
none: {
|
||||
navbarunfocused: "show",
|
||||
navtoolboxunfocused: "show",
|
||||
navbarafter: "hide",
|
||||
navbarnonaddonchildren: "hide",
|
||||
navbarnoheight: "show",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
189
src/excmds.ts
189
src/excmds.ts
|
@ -88,6 +88,7 @@
|
|||
// Shared
|
||||
import * as Messaging from "./messaging"
|
||||
import { l, browserBg, activeTabId, activeTabContainerId } from "./lib/webext"
|
||||
import * as Container from "./lib/containers"
|
||||
import state from "./state"
|
||||
import * as UrlUtil from "./url_util"
|
||||
import * as config from "./config"
|
||||
|
@ -220,6 +221,7 @@ import * as css_util from "./css_util"
|
|||
* - navbar
|
||||
* - always
|
||||
* - autohide
|
||||
* - none
|
||||
*
|
||||
* - hoverlink (the little link that appears when you hover over a link)
|
||||
* - none
|
||||
|
@ -300,13 +302,34 @@ export async function fixamo() {
|
|||
//#background
|
||||
export async function nativeopen(url: string, ...firefoxArgs: string[]) {
|
||||
if (await Native.nativegate()) {
|
||||
// First compute where the tab should be
|
||||
let pos = await config.getAsync("tabopenpos")
|
||||
let index = (await activeTab()).index + 1
|
||||
switch (pos) {
|
||||
case "last":
|
||||
index = 99999
|
||||
break
|
||||
case "related":
|
||||
// How do we simulate that?
|
||||
break
|
||||
}
|
||||
// Then make sure the tab is made active and moved to the right place
|
||||
// when it is opened in the current window
|
||||
let selecttab = tab => {
|
||||
browser.tabs.onCreated.removeListener(selecttab)
|
||||
tabSetActive(tab.id)
|
||||
browser.tabs.move(tab.id, { index })
|
||||
}
|
||||
browser.tabs.onCreated.addListener(selecttab)
|
||||
|
||||
if ((await browser.runtime.getPlatformInfo()).os === "mac") {
|
||||
let osascriptArgs = ["-e 'on run argv'", "-e 'tell application \"Firefox\" to open location item 1 of argv'", "-e 'end run'"]
|
||||
Native.run("osascript " + osascriptArgs.join(" ") + " " + url)
|
||||
await Native.run("osascript " + osascriptArgs.join(" ") + " " + url)
|
||||
} else {
|
||||
if (firefoxArgs.length === 0) firefoxArgs = ["--new-tab"]
|
||||
Native.run(config.get("browser") + " " + firefoxArgs.join(" ") + " " + url)
|
||||
await Native.run(config.get("browser") + " " + firefoxArgs.join(" ") + " " + url)
|
||||
}
|
||||
setTimeout(() => browser.tabs.onCreated.removeListener(selecttab), 100)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -806,8 +829,9 @@ export const ABOUT_WHITELIST = ["about:home", "about:license", "about:logo", "ab
|
|||
* - else treat as search parameters for google
|
||||
*
|
||||
* Related settings:
|
||||
* "searchengine": "google" or any of [[SEARCH_URLS]]
|
||||
* "historyresults": the n-most-recent results to ask Firefox for before they are sorted by frequency. Reduce this number if you find your results are bad.
|
||||
* - "searchengine": "google" or any of [[SEARCH_URLS]]
|
||||
* - "historyresults": the n-most-recent results to ask Firefox for before they are sorted by frequency. Reduce this number if you find your results are bad.
|
||||
*
|
||||
* Can only open about:* or file:* URLs if you have the native messenger installed, and on OSX you must set `browser` to something that will open Firefox from a terminal pass it commmand line options.
|
||||
*
|
||||
*/
|
||||
|
@ -1042,7 +1066,12 @@ export function urlincrement(count = 1) {
|
|||
let newUrl = UrlUtil.incrementUrl(window.location.href, count)
|
||||
|
||||
if (newUrl !== null) {
|
||||
window.location.href = newUrl
|
||||
// This might throw an error when using incrementurl on a moz-extension:// page if the page we're trying to access doesn't exist
|
||||
try {
|
||||
window.location.href = newUrl
|
||||
} catch (e) {
|
||||
logger.info(`urlincrement: Impossible to navigate to ${newUrl}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1231,9 +1260,12 @@ export async function reader() {
|
|||
//#content_helper
|
||||
loadaucmds("DocStart")
|
||||
|
||||
//#content_helper
|
||||
window.addEventListener("pagehide", () => loadaucmds("DocEnd"))
|
||||
|
||||
/** @hidden */
|
||||
//#content
|
||||
export async function loadaucmds(cmdType: "DocStart" | "TabEnter" | "TabLeft") {
|
||||
export async function loadaucmds(cmdType: "DocStart" | "DocEnd" | "TabEnter" | "TabLeft") {
|
||||
let aucmds = await config.getAsync("autocmds", cmdType)
|
||||
const ausites = Object.keys(aucmds)
|
||||
const aukeyarr = ausites.filter(e => window.document.location.href.search(e) >= 0)
|
||||
|
@ -1456,7 +1488,9 @@ export async function tablast() {
|
|||
|
||||
/** Like [[open]], but in a new tab. If no address is given, it will open the newtab page, which can be set with `set newtab [url]`
|
||||
|
||||
Use the `-b` flag as the first argument to open the tab in the background.
|
||||
Use the `-c` flag followed by a container name to open a tab in said container. Tridactyl will try to fuzzy match a name if an exact match is not found.
|
||||
Use the `-b` flag to open the tab in the background.
|
||||
These two can be combined in any order, but need to be placed as the first arguments.
|
||||
|
||||
Unlike Firefox's Ctrl-t shortcut, this opens tabs immediately after the
|
||||
currently active tab rather than at the end of the tab list because that is
|
||||
|
@ -1474,13 +1508,29 @@ export async function tablast() {
|
|||
//#background
|
||||
export async function tabopen(...addressarr: string[]) {
|
||||
let active
|
||||
if (addressarr[0] === "-b") {
|
||||
addressarr.shift()
|
||||
active = false
|
||||
let container
|
||||
|
||||
// Lets us pass both -b and -c in no particular order as long as they are up front.
|
||||
async function argParse(args): Promise<string[]> {
|
||||
if (args[0] === "-b") {
|
||||
active = false
|
||||
args.shift()
|
||||
argParse(args)
|
||||
} else if (args[0] === "-c") {
|
||||
// Ignore the -c flag if incognito as containers are disabled.
|
||||
let win = await browser.windows.getCurrent()
|
||||
if (!win["incognito"]) container = await Container.fuzzyMatch(args[1])
|
||||
else logger.error("[tabopen] can't open a container in a private browsing window.")
|
||||
|
||||
args.shift()
|
||||
args.shift()
|
||||
argParse(args)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
let url: string
|
||||
let address = addressarr.join(" ")
|
||||
let address = (await argParse(addressarr)).join(" ")
|
||||
|
||||
if (!ABOUT_WHITELIST.includes(address) && address.match(/^(about|file):.*/)) {
|
||||
if ((await browser.runtime.getPlatformInfo()).os === "mac" && (await browser.windows.getCurrent()).incognito) {
|
||||
|
@ -1494,7 +1544,9 @@ export async function tabopen(...addressarr: string[]) {
|
|||
else url = forceURI(config.get("newtab"))
|
||||
|
||||
activeTabContainerId().then(containerId => {
|
||||
if (containerId && config.get("tabopencontaineraware") === "true") openInNewTab(url, { active: active, cookieStoreId: containerId })
|
||||
// Ensure -c has priority.
|
||||
if (container) openInNewTab(url, { active: active, cookieStoreId: container })
|
||||
else if (containerId && config.get("tabopencontaineraware") === "true") openInNewTab(url, { active: active, cookieStoreId: containerId })
|
||||
else openInNewTab(url, { active })
|
||||
})
|
||||
}
|
||||
|
@ -1733,6 +1785,92 @@ export async function qall() {
|
|||
|
||||
// }}}
|
||||
|
||||
// {{{ CONTAINERS
|
||||
|
||||
/** Closes all tabs open in the same container across all windows.
|
||||
@param name The container name.
|
||||
*/
|
||||
//#background
|
||||
export async function containerclose(name: string) {
|
||||
let containerId = await Container.getId(name)
|
||||
browser.tabs.query({ cookieStoreId: containerId }).then(tabs => {
|
||||
browser.tabs.remove(
|
||||
tabs.map(tab => {
|
||||
return tab.id
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
/** Creates a new container. Note that container names must be unique and that the checks are case-insensitive.
|
||||
|
||||
Further reading https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/contextualIdentities/ContextualIdentity
|
||||
|
||||
Example usage:
|
||||
- `:containercreate tridactyl green dollar`
|
||||
|
||||
@param name The container name. Must be unique.
|
||||
@param color The container color. Valid colors are: "blue", "turquoise", "green", "yellow", "orange", "red", "pink", "purple". If no color is chosen a random one will be selected from the list of valid colors.
|
||||
@param icon The container icon. Valid icons are: "fingerprint", "briefcase", "dollar", "cart", "circle", "gift", "vacation", "food", "fruit", "pet", "tree", "chill". If no icon is chosen, it defaults to "fingerprint".
|
||||
*/
|
||||
//#background
|
||||
export async function containercreate(name: string, color?: string, icon?: string) {
|
||||
await Container.create(name, color, icon)
|
||||
}
|
||||
|
||||
/** Delete a container. Closes all tabs associated with that container beforehand. Note: container names are case-insensitive.
|
||||
@param name The container name.
|
||||
*/
|
||||
//#background
|
||||
export async function containerremove(name: string) {
|
||||
await containerclose(name)
|
||||
await Container.remove(name)
|
||||
}
|
||||
|
||||
/** Update a container's information. Note that none of the parameters are optional and that container names are case-insensitive.
|
||||
|
||||
Example usage:
|
||||
|
||||
- Changing the container name: `:containerupdate banking blockchain green dollar`
|
||||
|
||||
- Changing the container icon: `:containerupdate banking banking green briefcase`
|
||||
|
||||
- Changing the container color: `:containerupdate banking banking purple dollar`
|
||||
|
||||
@param name The container name.
|
||||
@param uname The new container name. Must be unique.
|
||||
@param ucolor The new container color. Valid colors are: "blue", "turquoise", "green", "yellow", "orange", "red", "pink", "purple". If no color is chosen a random one will be selected from the list of valid colors.
|
||||
@param uicon The new container icon. Valid icons are: "fingerprint", "briefcase", "dollar", "cart", "circle", "gift", "vacation", "food", "fruit", "pet", "tree", "chill".
|
||||
*/
|
||||
//#background
|
||||
export async function containerupdate(name: string, uname: string, ucolor: string, uicon: string) {
|
||||
logger.debug("containerupdate parameters: " + name + ", " + uname + ", " + ucolor + ", " + uicon)
|
||||
try {
|
||||
let containerId = await Container.fuzzyMatch(name)
|
||||
let containerObj = Container.fromString(uname, ucolor, uicon)
|
||||
await Container.update(containerId, containerObj)
|
||||
} catch (e) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/** Shows a list of the current containers in Firefox's native JSON viewer in the current tab.
|
||||
|
||||
NB: Tridactyl cannot run on this page!
|
||||
|
||||
*/
|
||||
//#content
|
||||
export async function viewcontainers() {
|
||||
// # and white space don't agree with FF's JSON viewer.
|
||||
// Probably other symbols too.
|
||||
let containers = await Container.getAll()
|
||||
window.location.href =
|
||||
"data:application/json," +
|
||||
JSON.stringify(containers)
|
||||
.replace(/#/g, "%23")
|
||||
.replace(/ /g, "%20")
|
||||
}
|
||||
// }}}
|
||||
//
|
||||
// {{{ MISC
|
||||
|
||||
/** Deprecated
|
||||
|
@ -2156,9 +2294,9 @@ export function set(key: string, ...values: string[]) {
|
|||
|
||||
/** Set autocmds to run when certain events happen.
|
||||
|
||||
@param event Curently, 'TriStart', 'DocStart', 'TabEnter' and 'TabLeft' are supported.
|
||||
@param event Curently, 'TriStart', 'DocStart', 'DocEnd', 'TabEnter' and 'TabLeft' are supported.
|
||||
|
||||
@param url For DocStart, TabEnter, and TabLeft: a fragment of the URL on which the events will trigger, or a JavaScript regex (e.g, `/www\.amazon\.co.*\/`)
|
||||
@param url For DocStart, DocEnd, TabEnter, and TabLeft: a fragment of the URL on which the events will trigger, or a JavaScript regex (e.g, `/www\.amazon\.co.*\/`)
|
||||
|
||||
We just use [URL.search](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search).
|
||||
|
||||
|
@ -2174,7 +2312,7 @@ export function set(key: string, ...values: string[]) {
|
|||
export function autocmd(event: string, url: string, ...excmd: string[]) {
|
||||
// rudimentary run time type checking
|
||||
// TODO: Decide on autocmd event names
|
||||
if (!["DocStart", "TriStart", "TabEnter", "TabLeft"].includes(event)) throw event + " is not a supported event."
|
||||
if (!["DocStart", "DocEnd", "TriStart", "TabEnter", "TabLeft"].includes(event)) throw event + " is not a supported event."
|
||||
config.set("autocmds", event, url, excmd.join(" "))
|
||||
}
|
||||
|
||||
|
@ -2452,9 +2590,24 @@ import * as hinting from "./hinting_background"
|
|||
To open a hint in the background, the default bind is `F`.
|
||||
|
||||
Related settings:
|
||||
"hintchars": "hjklasdfgyuiopqwertnmzxcvb"
|
||||
"hintfiltermode": "simple" | "vimperator" | "vimperator-reflow"
|
||||
"relatedopenpos": "related" | "next" | "last"
|
||||
- "hintchars": "hjklasdfgyuiopqwertnmzxcvb"
|
||||
- "hintfiltermode": "simple" | "vimperator" | "vimperator-reflow"
|
||||
- "relatedopenpos": "related" | "next" | "last"
|
||||
- "hintnames": "short" | "uniform" | "numeric"
|
||||
|
||||
With "short" names, Tridactyl will generate short hints that
|
||||
are never prefixes of each other. With "uniform", Tridactyl
|
||||
will generate hints of uniform length. In either case, the
|
||||
hints are generated from the set in "hintchars".
|
||||
|
||||
With "numeric" names, hints are always assigned using
|
||||
sequential integers, and "hintchars" is ignored. This has the
|
||||
disadvantage that some hints are prefixes of others (and you
|
||||
need to hit space or enter to select such a hint). But it has
|
||||
the advantage that the hints tend to be more predictable
|
||||
(e.g., a news site will have the same hints for its
|
||||
boilerplate each time you visit it, even if the number of
|
||||
links in the main body changes).
|
||||
*/
|
||||
//#background
|
||||
export function hint(option?: string, selectors?: string, ...rest: string[]) {
|
||||
|
|
|
@ -119,12 +119,21 @@ function defaultHintFilter() {
|
|||
}
|
||||
}
|
||||
|
||||
function defaultHintChars() {
|
||||
switch (config.get("hintnames")) {
|
||||
case "numeric":
|
||||
return "1234567890"
|
||||
default:
|
||||
return config.get("hintchars")
|
||||
}
|
||||
}
|
||||
|
||||
/** An infinite stream of hints
|
||||
|
||||
Earlier hints prefix later hints
|
||||
*/
|
||||
function* hintnames_simple(
|
||||
hintchars = config.get("hintchars"),
|
||||
hintchars = defaultHintChars(),
|
||||
): IterableIterator<string> {
|
||||
for (let taglen = 1; true; taglen++) {
|
||||
yield* map(permutationsWithReplacement(hintchars, taglen), e =>
|
||||
|
@ -147,9 +156,9 @@ function* hintnames_simple(
|
|||
and so on, but we hardly ever see that many hints, so whatever.
|
||||
|
||||
*/
|
||||
function* hintnames(
|
||||
function* hintnames_short(
|
||||
n: number,
|
||||
hintchars = config.get("hintchars"),
|
||||
hintchars = defaultHintChars(),
|
||||
): IterableIterator<string> {
|
||||
let source = hintnames_simple(hintchars)
|
||||
const num2skip = Math.floor(n / hintchars.length)
|
||||
|
@ -159,7 +168,7 @@ function* hintnames(
|
|||
/** Uniform length hintnames */
|
||||
function* hintnames_uniform(
|
||||
n: number,
|
||||
hintchars = config.get("hintchars"),
|
||||
hintchars = defaultHintChars(),
|
||||
): IterableIterator<string> {
|
||||
if (n <= hintchars.length) yield* islice(hintchars[Symbol.iterator](), n)
|
||||
else {
|
||||
|
@ -175,6 +184,26 @@ function* hintnames_uniform(
|
|||
}
|
||||
}
|
||||
|
||||
function* hintnames_numeric(n: number): IterableIterator<string> {
|
||||
for (let i = 1; i <= n; i++) {
|
||||
yield String(i)
|
||||
}
|
||||
}
|
||||
|
||||
function* hintnames(
|
||||
n: number,
|
||||
hintchars = defaultHintChars(),
|
||||
): IterableIterator<string> {
|
||||
switch (config.get("hintnames")) {
|
||||
case "numeric":
|
||||
yield* hintnames_numeric(n)
|
||||
case "uniform":
|
||||
yield* hintnames_uniform(n, hintchars)
|
||||
default:
|
||||
yield* hintnames_short(n, hintchars)
|
||||
}
|
||||
}
|
||||
|
||||
type HintSelectedCallback = (Hint) => any
|
||||
|
||||
/** Place a flag by each hintworthy element */
|
||||
|
@ -256,9 +285,7 @@ function buildHintsVimperator(els: Element[], onSelect: HintSelectedCallback) {
|
|||
let names = hintnames(els.length)
|
||||
// escape the hintchars string so that strange things don't happen
|
||||
// when special characters are used as hintchars (for example, ']')
|
||||
const escapedHintChars = config
|
||||
.get("hintchars")
|
||||
.replace(/^\^|[-\\\]]/g, "\\$&")
|
||||
const escapedHintChars = defaultHintChars().replace(/^\^|[-\\\]]/g, "\\$&")
|
||||
const filterableTextFilter = new RegExp("[" + escapedHintChars + "]", "g")
|
||||
for (let [el, name] of izip(els, names)) {
|
||||
let ft = elementFilterableText(el)
|
||||
|
@ -322,7 +349,7 @@ function filterHintsVimperator(fstr, reflow = false) {
|
|||
/** Partition a fstr into a tagged array of substrings */
|
||||
function partitionFstr(fstr): { str: string; isHintChar: boolean }[] {
|
||||
const peek = a => a[a.length - 1]
|
||||
const hintChars = config.get("hintchars")
|
||||
const hintChars = defaultHintChars()
|
||||
|
||||
// For each char, either add it to the existing run if there is one and
|
||||
// it's a matching type or start a new run
|
||||
|
|
231
src/lib/containers.ts
Normal file
231
src/lib/containers.ts
Normal file
|
@ -0,0 +1,231 @@
|
|||
import * as Logging from "../logging"
|
||||
const logger = new Logging.Logger("containers")
|
||||
|
||||
// As per Mozilla specification: https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/contextualIdentities/ContextualIdentity
|
||||
const ContainerColor = [
|
||||
"blue",
|
||||
"turquoise",
|
||||
"green",
|
||||
"yellow",
|
||||
"orange",
|
||||
"red",
|
||||
"pink",
|
||||
"purple",
|
||||
]
|
||||
|
||||
// As per Mozilla specification: https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/contextualIdentities/ContextualIdentity
|
||||
const ContainerIcon = [
|
||||
"fingerprint",
|
||||
"briefcase",
|
||||
"dollar",
|
||||
"cart",
|
||||
"circle",
|
||||
"gift",
|
||||
"vacation",
|
||||
"food",
|
||||
"fruit",
|
||||
"pet",
|
||||
"tree",
|
||||
"chill",
|
||||
]
|
||||
|
||||
/** Creates a container from the specified parameters.Does not allow multiple containers with the same name.
|
||||
@param name The container name.
|
||||
@param color The container color, must be one of: "blue", "turquoise", "green", "yellow", "orange", "red", "pink" or "purple". If nothing is supplied, it selects one at random.
|
||||
@param icon The container icon, must be one of: "fingerprint", "briefcase", "dollar", "cart", "circle", "gift", "vacation", "food", "fruit", "pet", "tree", "chill"
|
||||
*/
|
||||
export async function create(
|
||||
name: string,
|
||||
color = "random",
|
||||
icon = "fingerprint",
|
||||
): Promise<string> {
|
||||
if (color === "random") color = chooseRandomColor()
|
||||
let container = fromString(name, color, icon)
|
||||
logger.debug(container)
|
||||
|
||||
if (await exists(name)) {
|
||||
logger.debug(`[Container.create] container already exists ${container}`)
|
||||
throw new Error(
|
||||
`[Container.create] container already exists, aborting.`,
|
||||
)
|
||||
} else {
|
||||
try {
|
||||
let res = await browser.contextualIdentities.create(container)
|
||||
logger.info(
|
||||
"[Container.create] created container:",
|
||||
res["cookieStoreId"],
|
||||
)
|
||||
return res["cookieStoreId"]
|
||||
} catch (e) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes specified container. No fuzzy matching is intentional here. If there are multiple containers with the same name (allowed by other container plugins), it chooses the one with the lowest cookieStoreId
|
||||
@param name The container name
|
||||
*/
|
||||
export async function remove(name: string) {
|
||||
logger.debug(name)
|
||||
try {
|
||||
let id = await getId(name)
|
||||
let res = await browser.contextualIdentities.remove(id)
|
||||
logger.debug("[Container.remove] removed container:", res.cookieStoreId)
|
||||
} catch (e) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/** Updates the specified container.
|
||||
TODO: pass an object to this when tridactyl gets proper flag parsing
|
||||
NOTE: while browser.contextualIdentities.create does check for valid color/icon combos, browser.contextualIdentities.update does not.
|
||||
@param containerId Expects a cookieStringId e.g. "firefox-container-n".
|
||||
@param name the new name of the container
|
||||
@param color the new color of the container
|
||||
@param icon the new icon of the container
|
||||
*/
|
||||
export async function update(
|
||||
containerId: string,
|
||||
updateObj: {
|
||||
name: string
|
||||
color: browser.contextualIdentities.IdentityColor
|
||||
icon: browser.contextualIdentities.IdentityIcon
|
||||
},
|
||||
) {
|
||||
if (isValidColor(updateObj["color"]) && isValidIcon(updateObj["icon"])) {
|
||||
try {
|
||||
browser.contextualIdentities.update(containerId, updateObj)
|
||||
} catch (e) {
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
logger.debug(updateObj)
|
||||
throw new Error("[Container.update] invalid container icon or color")
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a container object from a supplied container id string.
|
||||
@param containerId Expects a cookieStringId e.g. "firefox-container-n"
|
||||
*/
|
||||
export async function getFromId(containerId: string): Promise<{}> {
|
||||
try {
|
||||
return await browser.contextualIdentities.get(containerId)
|
||||
} catch (e) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetches all containers from Firefox's contextual identities API and checks if one exists with the specified name.
|
||||
Note: This operation is entirely case-insensitive.
|
||||
@param string cname
|
||||
@returns boolean Returns true when cname matches an existing container or on query error.
|
||||
*/
|
||||
export async function exists(cname: string): Promise<boolean> {
|
||||
let exists = false
|
||||
try {
|
||||
let containers = await getAll()
|
||||
let res = containers.filter(c => {
|
||||
return c.name.toLowerCase() === cname
|
||||
})
|
||||
if (res.length > 0) {
|
||||
exists = true
|
||||
}
|
||||
} catch (e) {
|
||||
exists = true // Make sure we don't accidentally break the constraint on query error.
|
||||
logger.error(
|
||||
"[Container.exists] Error querying contextualIdentities:",
|
||||
e,
|
||||
)
|
||||
}
|
||||
return exists
|
||||
}
|
||||
|
||||
/** Takes string parameters and returns them as a pseudo container object
|
||||
for use in other functions in the library.
|
||||
@param name
|
||||
@param color
|
||||
@param icon
|
||||
*/
|
||||
export function fromString(name: string, color: string, icon: string) {
|
||||
try {
|
||||
return {
|
||||
name: name,
|
||||
color: color as browser.contextualIdentities.IdentityColor,
|
||||
icon: icon as browser.contextualIdentities.IdentityIcon,
|
||||
}
|
||||
} catch (e) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns An array representation of all containers.
|
||||
*/
|
||||
export async function getAll(): Promise<any[]> {
|
||||
return await browser.contextualIdentities.query({})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name The container name
|
||||
* @returns The cookieStoreId of the first match of the query.
|
||||
*/
|
||||
export async function getId(name: string): Promise<string> {
|
||||
try {
|
||||
return (await browser.contextualIdentities.query({ name: name }))[0][
|
||||
"cookieStoreId"
|
||||
]
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
"[Container.getId] could not find a container with that name.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Tries some simple ways to match containers to your input.
|
||||
Fuzzy matching is entirely case-insensitive.
|
||||
@param partialName The (partial) name of the container.
|
||||
*/
|
||||
export async function fuzzyMatch(partialName: string): Promise<string> {
|
||||
let containers = await getAll()
|
||||
let exactMatch = containers.filter(c => {
|
||||
return c.name.toLowerCase() === partialName
|
||||
})
|
||||
|
||||
if (exactMatch.length === 1) {
|
||||
return exactMatch[0]["cookieStoreId"]
|
||||
} else if (exactMatch.length > 1) {
|
||||
throw new Error(
|
||||
"[Container.fuzzyMatch] more than one container with this name exists.",
|
||||
)
|
||||
} else {
|
||||
let fuzzyMatches = containers.filter(c => {
|
||||
return c.name.toLowerCase().indexOf(partialName) > -1
|
||||
})
|
||||
if (fuzzyMatches.length === 1) {
|
||||
return fuzzyMatches[0]["cookieStoreId"]
|
||||
} else if (fuzzyMatches.length > 1) {
|
||||
throw new Error(
|
||||
"[Container.fuzzyMatch] ambiguous match, provide more characters",
|
||||
)
|
||||
} else {
|
||||
throw new Error(
|
||||
"[Container.fuzzyMatch] no container matched that string",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Helper function for create, returns a random valid IdentityColor for use if no color is applied at creation.*/
|
||||
function chooseRandomColor(): string {
|
||||
let max = Math.floor(ContainerColor.length)
|
||||
let n = Math.floor(Math.random() * max)
|
||||
return ContainerColor[n]
|
||||
}
|
||||
|
||||
function isValidColor(color: string): boolean {
|
||||
return ContainerColor.indexOf(color) > -1
|
||||
}
|
||||
|
||||
function isValidIcon(icon: string): boolean {
|
||||
return ContainerIcon.indexOf(icon) > -1
|
||||
}
|
|
@ -72,6 +72,17 @@ export async function activeTabContainerId() {
|
|||
return (await activeTab()).cookieStoreId
|
||||
}
|
||||
|
||||
//#background_helper
|
||||
export async function activeTabContainer() {
|
||||
let containerId = await activeTabContainerId()
|
||||
if (containerId !== "firefox-default")
|
||||
return await browserBg.contextualIdentities.get(containerId)
|
||||
else
|
||||
throw new Error(
|
||||
"firefox-default is not a valid contextualIdentity (activeTabContainer)",
|
||||
)
|
||||
}
|
||||
|
||||
/** Compare major firefox versions */
|
||||
export async function firefoxVersionAtLeast(desiredmajor: number) {
|
||||
const versionstr = (await browserBg.runtime.getBrowserInfo()).version
|
||||
|
|
37
src/newtab.ts
Normal file
37
src/newtab.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
// This file is only included in newtab.html, after content.js has been loaded
|
||||
|
||||
// These functions work with the elements created by tridactyl/scripts/newtab.md.sh
|
||||
function getChangelogDiv() {
|
||||
const changelogDiv = document.getElementById("changelog")
|
||||
if (!changelogDiv) throw new Error("Couldn't find changelog element!")
|
||||
return changelogDiv
|
||||
}
|
||||
|
||||
function updateChangelogStatus() {
|
||||
const changelogDiv = getChangelogDiv()
|
||||
const changelogContent = changelogDiv.textContent
|
||||
if (localStorage.changelogContent == changelogContent) {
|
||||
const changelogButton = document.querySelector('input[id^="spoiler"]')
|
||||
if (!changelogButton) {
|
||||
console.error("Couldn't find changelog button!")
|
||||
return
|
||||
}
|
||||
changelogButton.classList.add("seen")
|
||||
}
|
||||
}
|
||||
|
||||
function readChangelog() {
|
||||
const changelogDiv = getChangelogDiv()
|
||||
localStorage.changelogContent = changelogDiv.textContent
|
||||
updateChangelogStatus()
|
||||
}
|
||||
|
||||
window.addEventListener("load", updateChangelogStatus)
|
||||
window.addEventListener("load", _ => {
|
||||
const spoilerbutton = document.getElementById("spoilerbutton")
|
||||
if (!spoilerbutton) {
|
||||
console.error("Couldn't find spoiler button!")
|
||||
return
|
||||
}
|
||||
spoilerbutton.addEventListener("click", readChangelog)
|
||||
})
|
|
@ -18,4 +18,4 @@ We support a handful of keybinds in the console:
|
|||
* `Ctrl-F` to complete the command from command history
|
||||
* `Space` to insert the URL of the highlighted completion into the command line
|
||||
|
||||
The [next page](./settings.html) will talk about the various settings available.
|
||||
The [next page](./settings.html) will talk about the various settings available. <a href='./hint_mode.html' rel="prev"></a>
|
||||
|
|
|
@ -8,4 +8,4 @@ You can get help about any command by typing `help [command]` in command mode. A
|
|||
|
||||
Lastly, you can contact the developers via Matrix or GitHub, as mentioned on the new tab page.
|
||||
|
||||
This concludes the tutorial. If you have any feedback, please leave it on [the relevant GitHub issue](https://github.com/cmcaine/tridactyl/issues/380).
|
||||
This concludes the tutorial. If you have any feedback, please leave it on [the relevant GitHub issue](https://github.com/cmcaine/tridactyl/issues/380). <a href='./settings.html' rel="prev"></a>
|
||||
|
|
|
@ -12,4 +12,4 @@ Here are some of the most useful hint modes:
|
|||
|
||||
If there is ever only a single hint remaining (for example, because you have wittled them down, or there is only a single link visible on the page) the hint mode will follow it automatically.
|
||||
|
||||
The [next page](./command_mode.html) will cover the command mode.
|
||||
The [next page](./command_mode.html) will cover the command mode. <a href='./normal_mode.html' rel="prev"></a>
|
||||
|
|
|
@ -29,3 +29,5 @@ Many keypresses in normal mode take you into another mode. `t`, for example, put
|
|||
All the keys in normal mode are bound to commands; for example, `j` is bound to `scrolline 10`. If you are ever curious as to what a key sequence does in normal mode, you can simply use `:bind [keys]` and the command line will tell you to which command they are bound.
|
||||
|
||||
The [next page](./hint_mode.html) will explain how to use some of the various hint modes. This time try `]]` (guess the next page) to follow the link.
|
||||
|
||||
<a href='./tutor.html' rel="prev"></a>
|
||||
|
|
|
@ -18,4 +18,4 @@ Here we will briefly summarise some of the main settings:
|
|||
* excmds
|
||||
* aliases for command mode: the things on the left actually run the commands on the right. The most interesting one of these is `current_url`, which is how the binds for O, W and T (`bind T`) work.
|
||||
|
||||
The <a href='./help.html' rel='next'>final page</a> describes how you can get further help.
|
||||
The <a href='./help.html' rel='next'>final page</a> describes how you can get further help. <a href='./command_mode.html' rel="prev"></a>
|
||||
|
|
|
@ -30,6 +30,15 @@ input[id^="spoiler"]:checked + label {
|
|||
color: #333;
|
||||
background: #ccc;
|
||||
}
|
||||
input[id^="spoiler"]:checked + label > #nagbar-changelog,
|
||||
input[id^="spoiler"].seen + label > #nagbar-changelog {
|
||||
display: none;
|
||||
}
|
||||
#nagbar-changelog {
|
||||
font-size: 8pt;
|
||||
background: red;
|
||||
line-height: 1.4;
|
||||
}
|
||||
input[id^="spoiler"] ~ .spoiler {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -41,8 +41,8 @@ REPLACE_ME_WITH_THE_CHANGE_LOG_USING_SED
|
|||
|
||||
## Important limitations due to WebExtensions
|
||||
|
||||
* You can only navigate to most about:_\file:_ pages if you have Tridactyl's native executable installed.
|
||||
* Firefox will not load Tridactyl on addons.mozilla.org, about:\*, some file:\* URIs, view-source:\*, or data:\*. On these pages Ctrl-L (or F6), Ctrl-Tab and Ctrl-W are your escape hatches.
|
||||
* You can only navigate to most about: and file: pages if you have Tridactyl's native executable installed.
|
||||
* Firefox will not load Tridactyl on about:\*, some file:\* URIs, view-source:\*, or data:\*. On these pages Ctrl-L (or F6), Ctrl-Tab and Ctrl-W are your escape hatches.
|
||||
* You can change the Firefox GUI with `guiset` (e.g. `guiset gui none` and then `restart`) if you have the native messenger installed, or you can do it yourself by changing your userChrome. There is an example file available on our repository [[2]].
|
||||
* Tridactyl cannot capture key presses until web pages are loaded. You can use `:reloadall` to reload all tabs to make life more bearable, or flip `browser.sessionstore.restore_tabs_lazily` to false in `about:config`.
|
||||
|
||||
|
|
|
@ -13,4 +13,5 @@
|
|||
REPLACETHIS
|
||||
</body>
|
||||
<script src="../content.js"></script>
|
||||
<script src="../newtab.js"></script>
|
||||
</html>
|
||||
|
|
|
@ -8,6 +8,7 @@ module.exports = {
|
|||
content: "./src/content.ts",
|
||||
commandline_frame: "./src/commandline_frame.ts",
|
||||
help: "./src/help.ts",
|
||||
newtab: "./src/newtab.ts",
|
||||
},
|
||||
output: {
|
||||
filename: "[name].js",
|
||||
|
|
Loading…
Add table
Reference in a new issue