mirror of
https://github.com/vale981/tridactyl
synced 2025-03-04 17:11:40 -05:00
Merge branch 'config_api'
This commit is contained in:
commit
be2618c352
5 changed files with 145 additions and 38 deletions
|
@ -8,6 +8,9 @@ export function toSimpleType(typeNode) {
|
|||
switch (typeNode.kind) {
|
||||
case ts.SyntaxKind.VoidKeyword:
|
||||
return new AllTypes.VoidType()
|
||||
// IndexedAccessTypes are things like `fn<T keyof Class>(x: T, y: Class[T])`
|
||||
// This doesn't seem to be easy to deal with so let's kludge it for now
|
||||
case ts.SyntaxKind.IndexedAccessType:
|
||||
case ts.SyntaxKind.AnyKeyword:
|
||||
return new AllTypes.AnyType()
|
||||
case ts.SyntaxKind.BooleanKeyword:
|
||||
|
@ -26,7 +29,9 @@ export function toSimpleType(typeNode) {
|
|||
if (!typeNode.typeArguments) {
|
||||
// If there are no typeArguments, this is not a parametric type and we can return the type directly
|
||||
try {
|
||||
return toSimpleType(typeNode.typeName.symbol.declarations[0].type)
|
||||
return toSimpleType(
|
||||
typeNode.typeName.symbol.declarations[0].type,
|
||||
)
|
||||
} catch (e) {
|
||||
// Fall back to what you'd do with typeArguments
|
||||
}
|
||||
|
@ -47,16 +52,21 @@ export function toSimpleType(typeNode) {
|
|||
toSimpleType(typeNode.type),
|
||||
)
|
||||
case ts.SyntaxKind.TypeLiteral:
|
||||
let members = typeNode.members.map(member => {
|
||||
if (member.kind == ts.SyntaxKind.IndexSignature) {
|
||||
// Something like this: { [str: string]: string[] }
|
||||
return ["", toSimpleType(member.type)]
|
||||
}
|
||||
// Very fun feature: when you have an object literal with >20 members, typescript will decide to replace some of them with a "... X more ..." node that obviously doesn't have a corresponding symbol, hence this check and the filter after the map
|
||||
if (member.name.symbol)
|
||||
return [member.name.symbol.escapedName, toSimpleType(member.type)];
|
||||
}).filter(m => m)
|
||||
return new AllTypes.ObjectType(new Map(members));
|
||||
let members = typeNode.members
|
||||
.map(member => {
|
||||
if (member.kind == ts.SyntaxKind.IndexSignature) {
|
||||
// Something like this: { [str: string]: string[] }
|
||||
return ["", toSimpleType(member.type)]
|
||||
}
|
||||
// Very fun feature: when you have an object literal with >20 members, typescript will decide to replace some of them with a "... X more ..." node that obviously doesn't have a corresponding symbol, hence this check and the filter after the map
|
||||
if (member.name.symbol)
|
||||
return [
|
||||
member.name.symbol.escapedName,
|
||||
toSimpleType(member.type),
|
||||
]
|
||||
})
|
||||
.filter(m => m)
|
||||
return new AllTypes.ObjectType(new Map(members))
|
||||
case ts.SyntaxKind.ArrayType:
|
||||
return new AllTypes.ArrayType(toSimpleType(typeNode.elementType))
|
||||
case ts.SyntaxKind.TupleType:
|
||||
|
@ -71,6 +81,8 @@ export function toSimpleType(typeNode) {
|
|||
case ts.SyntaxKind.LiteralType:
|
||||
return new AllTypes.LiteralTypeType(typeNode.literal.text)
|
||||
break
|
||||
case ts.SyntaxKind.IndexedAccessType:
|
||||
|
||||
default:
|
||||
console.log(typeNode)
|
||||
throw new Error(`Unhandled kind (${typeNode.kind}) for ${typeNode}`)
|
||||
|
@ -89,10 +101,22 @@ function isNodeExported(node: ts.Node): boolean {
|
|||
|
||||
/** True if node is marked as @hidden in its documentation */
|
||||
function isNodeHidden(sourceFile, node): boolean {
|
||||
return sourceFile && node.jsDoc && !!node.jsDoc.find(doc => sourceFile.text.slice(doc.pos, doc.end).search("@hidden") != -1)
|
||||
return (
|
||||
sourceFile &&
|
||||
node.jsDoc &&
|
||||
!!node.jsDoc.find(
|
||||
doc =>
|
||||
sourceFile.text.slice(doc.pos, doc.end).search("@hidden") != -1,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function visit(checker: any, sourceFile: any, file: AllMetadata.FileMetadata, node: any) {
|
||||
function visit(
|
||||
checker: any,
|
||||
sourceFile: any,
|
||||
file: AllMetadata.FileMetadata,
|
||||
node: any,
|
||||
) {
|
||||
let symbol = checker.getSymbolAtLocation(node.name)
|
||||
if (symbol && isNodeExported(node)) {
|
||||
let nodeName = symbol.escapedName
|
||||
|
@ -114,7 +138,11 @@ function visit(checker: any, sourceFile: any, file: AllMetadata.FileMetadata, no
|
|||
: new AllTypes.AnyType()
|
||||
file.setFunction(
|
||||
nodeName,
|
||||
new AllMetadata.SymbolMetadata(doc, t, isNodeHidden(sourceFile, node)),
|
||||
new AllMetadata.SymbolMetadata(
|
||||
doc,
|
||||
t,
|
||||
isNodeHidden(sourceFile, node),
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -145,7 +173,11 @@ function visit(checker: any, sourceFile: any, file: AllMetadata.FileMetadata, no
|
|||
: new AllTypes.AnyType()
|
||||
clazz.setMember(
|
||||
name,
|
||||
new AllMetadata.SymbolMetadata(doc, t, isNodeHidden(sourceFile, node)),
|
||||
new AllMetadata.SymbolMetadata(
|
||||
doc,
|
||||
t,
|
||||
isNodeHidden(sourceFile, node),
|
||||
),
|
||||
)
|
||||
})
|
||||
return
|
||||
|
@ -202,7 +234,8 @@ function generateMetadata(
|
|||
}
|
||||
|
||||
// We need to specify Type itself because it won't exist in AllTypes.js since it's an interface
|
||||
let imports = `import { Type } from "../compiler/types/AllTypes"\n` +
|
||||
let imports =
|
||||
`import { Type } from "../compiler/types/AllTypes"\n` +
|
||||
`import {${Object.keys(AllTypes).join(
|
||||
", ",
|
||||
)}} from "../compiler/types/AllTypes"\n` +
|
||||
|
|
|
@ -79,13 +79,11 @@ function removeCSPListener() {
|
|||
|
||||
config.getAsync("csp").then(csp => csp === "clobber" && addCSPListener())
|
||||
|
||||
browser.storage.onChanged.addListener((changes, areaname) => {
|
||||
if ("userconfig" in changes) {
|
||||
if (changes.userconfig.newValue.csp === "clobber") {
|
||||
addCSPListener()
|
||||
} else {
|
||||
removeCSPListener()
|
||||
}
|
||||
config.addChangeListener("csp", (old, cur) => {
|
||||
if (cur === "clobber") {
|
||||
addCSPListener()
|
||||
} else {
|
||||
removeCSPListener()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -14,14 +14,9 @@ async function getDuration() {
|
|||
opts.duration = await config.getAsync("scrollduration")
|
||||
return opts.duration
|
||||
}
|
||||
browser.storage.onChanged.addListener(changes => {
|
||||
if ("userconfig" in changes) {
|
||||
if ("smoothscroll" in changes.userconfig.newValue)
|
||||
opts.smooth = changes.userconfig.newValue["smoothscroll"]
|
||||
if ("scrollduration" in changes.userconfig.newValue)
|
||||
opts.duration = changes.userconfig.newValue["scrollduration"]
|
||||
}
|
||||
})
|
||||
|
||||
config.addChangeListener("smoothscroll", (prev, cur) => opts.smooth = cur)
|
||||
config.addChangeListener("scrollduration", (prev, cur) => opts.duration = cur)
|
||||
|
||||
class ScrollingData {
|
||||
// time at which the scrolling animation started
|
||||
|
|
|
@ -80,12 +80,7 @@ function retheme() {
|
|||
})
|
||||
}
|
||||
|
||||
// Hacky listener
|
||||
browser.storage.onChanged.addListener((changes, areaname) => {
|
||||
if ("userconfig" in changes) {
|
||||
retheme()
|
||||
}
|
||||
})
|
||||
config.addChangeListener("theme", retheme)
|
||||
|
||||
// Sometimes pages will overwrite class names of elements. We use a MutationObserver to make sure that the HTML element always has a TridactylTheme class
|
||||
// We can't just call theme() because it would first try to remove class names from the element, which would trigger the MutationObserver before we had a chance to add the theme class and thus cause infinite recursion
|
||||
|
|
|
@ -810,6 +810,9 @@ function setDeepProperty(obj, value, target) {
|
|||
}
|
||||
}
|
||||
|
||||
/** @hidden
|
||||
* Merges two objects and any child objects they may have
|
||||
*/
|
||||
export function mergeDeep(o1, o2) {
|
||||
let r = Array.isArray(o1) ? o1.slice() : Object.create(o1)
|
||||
Object.assign(r, o1, o2)
|
||||
|
@ -820,7 +823,11 @@ export function mergeDeep(o1, o2) {
|
|||
return r
|
||||
}
|
||||
|
||||
export function getURL(url, target) {
|
||||
/** @hidden
|
||||
* Gets a site-specific setting.
|
||||
*/
|
||||
|
||||
export function getURL(url: string, target: string[]) {
|
||||
if (!USERCONFIG.subconfigs) return undefined
|
||||
// For each key
|
||||
return (
|
||||
|
@ -1145,16 +1152,95 @@ async function init() {
|
|||
}
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
const changeListeners = new Map()
|
||||
|
||||
/** @hidden
|
||||
* @param name The name of a "toplevel" config setting (i.e. "nmaps", not "nmaps.j")
|
||||
* @param listener A function to call when the value of $name is modified in the config. Takes the previous and new value as parameters.
|
||||
*/
|
||||
export function addChangeListener<P extends keyof default_config>(
|
||||
name: P,
|
||||
listener: (old: default_config[P], neww: default_config[P]) => void,
|
||||
) {
|
||||
let arr = changeListeners.get(name)
|
||||
if (!arr) {
|
||||
arr = []
|
||||
changeListeners.set(name, arr)
|
||||
}
|
||||
arr.push(listener)
|
||||
}
|
||||
|
||||
/** @hidden
|
||||
* Removes event listeners set with addChangeListener
|
||||
*/
|
||||
export function removeChangeListener<P extends keyof default_config>(
|
||||
name: P,
|
||||
listener: (old: default_config[P], neww: default_config[P]) => void,
|
||||
) {
|
||||
let arr = changeListeners.get(name)
|
||||
if (!arr) return
|
||||
let i = arr.indexOf(listener)
|
||||
if (i >= 0) arr.splice(i, 1)
|
||||
}
|
||||
|
||||
// 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(async (changes, areaname) => {
|
||||
if (CONFIGNAME in changes) {
|
||||
let defaultConf = new default_config()
|
||||
|
||||
// newValue is undefined when calling browser.storage.AREANAME.clear()
|
||||
if (changes[CONFIGNAME].newValue !== undefined) {
|
||||
// A key has been :unset if it exists in USERCONFIG and doesn't in changes and if its value in USERCONFIG is different from the one it has in default_config
|
||||
let unsetKeys = Object.keys(USERCONFIG).filter(
|
||||
k =>
|
||||
changes[CONFIGNAME].newValue[k] === undefined &&
|
||||
JSON.stringify(USERCONFIG[k]) !=
|
||||
JSON.stringify(defaultConf[k]),
|
||||
)
|
||||
|
||||
// A key has changed if it is defined in USERCONFIG and its value in USERCONFIG is different from the one in `changes` or if the value in defaultConf is different from the one in `changes`
|
||||
let changedKeys = Object.keys(changes[CONFIGNAME].newValue).filter(
|
||||
k =>
|
||||
JSON.stringify(
|
||||
USERCONFIG[k] !== undefined
|
||||
? USERCONFIG[k]
|
||||
: defaultConf[k],
|
||||
) != JSON.stringify(changes[CONFIGNAME].newValue[k]),
|
||||
)
|
||||
|
||||
let old = USERCONFIG
|
||||
USERCONFIG = changes[CONFIGNAME].newValue
|
||||
|
||||
// Trigger listeners
|
||||
unsetKeys.forEach(key => {
|
||||
let arr = changeListeners.get(key)
|
||||
if (arr) {
|
||||
arr.forEach(f => f(old[key], defaultConf[key]))
|
||||
}
|
||||
})
|
||||
|
||||
changedKeys.forEach(key => {
|
||||
let arr = changeListeners.get(key)
|
||||
if (arr) {
|
||||
let v = old[key] === undefined ? defaultConf[key] : old[key]
|
||||
arr.forEach(f => f(v, USERCONFIG[key]))
|
||||
}
|
||||
})
|
||||
} 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
|
||||
let old = USERCONFIG
|
||||
USERCONFIG = o({})
|
||||
|
||||
Object.keys(old)
|
||||
.filter(key => old[key] != defaultConf[key])
|
||||
.forEach(key => {
|
||||
let arr = changeListeners.get(key)
|
||||
if (arr) {
|
||||
arr.forEach(f => f(old[key], defaultConf[key]))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue