mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2025-04-11 21:33:13 +00:00
Merge 2eaadcd1c63c744461f85c6ba0a5ba9c027f5e6f into c3695765ad8b3e46b4dcc89496a2daa331215a81
This commit is contained in:
commit
927291b74f
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,3 +9,4 @@ node_modules/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
$__StoryList.tid
|
||||
types/core/
|
||||
|
@ -4,4 +4,8 @@
|
||||
|
||||
./bin/clean.sh
|
||||
|
||||
# Build typings
|
||||
npm i typescript
|
||||
npx tsc
|
||||
|
||||
npm publish || exit 1
|
||||
|
56
core/modules/parsers/base.js
Normal file
56
core/modules/parsers/base.js
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Represents an attribute in a parse tree node
|
||||
*
|
||||
* @typedef {Object} ParseTreeAttribute
|
||||
* @property {number} [end] - End position of attribute in source text
|
||||
* @property {string} [name] - Name of attribute
|
||||
* @property {number} [start] - Start position of attribute in source text
|
||||
* @property {'string' | 'number' | 'bigint' | 'boolean' | 'macro' | 'macro-parameter'} type - Type of attribute
|
||||
* @property {string | IMacroCallParseTreeNode} value - Actual value of attribute
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base structure for a parse node
|
||||
*
|
||||
* @typedef {Object} ParseTreeNode
|
||||
* @property {string} type - Type of widget that will render this node
|
||||
* @property {string} rule - Parse rule that generated this node. One rule can generate multiple types of nodes
|
||||
* @property {number} start - Rule start marker in source text
|
||||
* @property {number} end - Rule end marker in source text
|
||||
* @property {Record<string,ParseTreeAttribute>} [attributes] - Attributes of widget
|
||||
* @property {ParseTreeNode[]} [children] - Array of child parse nodes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for parsers. This only provides typing
|
||||
*
|
||||
* @class
|
||||
* @param {string} type - Content type of text to be parsed
|
||||
* @param {string} text - Text to be parsed
|
||||
* @param {Object} options - Parser options
|
||||
* @param {boolean} [options.parseAsInline=false] - If true, text will be parsed as an inline run
|
||||
* @param {Object} options.wiki - Reference to wiki store in use
|
||||
* @param {string} [options._canonical_uri] - Optional URI of content if text is missing or empty
|
||||
* @param {boolean} [options.configTrimWhiteSpace=false] - If true, parser trims white space
|
||||
*/
|
||||
function Parser(type, text, options) {
|
||||
/**
|
||||
* Result AST
|
||||
* @type {ParseTreeNode[]}
|
||||
*/
|
||||
this.tree = [];
|
||||
|
||||
/**
|
||||
* Original text without modifications
|
||||
* @type {string}
|
||||
*/
|
||||
this.source = text;
|
||||
|
||||
/**
|
||||
* Source content type in MIME format
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
exports.Parser = Parser;
|
@ -12,6 +12,27 @@ Wiki text rule for code blocks. For example:
|
||||
```
|
||||
|
||||
\*/
|
||||
|
||||
/**
|
||||
* @typedef {import("$:/core/modules/parsers/base.js").ParseTreeAttribute} ParseTreeAttribute
|
||||
* @typedef {import('../wikirulebase.js').WikiRuleBase} WikiRuleBase
|
||||
* @typedef {import('../../base.js').Parser} Parser
|
||||
* @typedef {typeof exports & WikiRuleBase} ThisRule
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the `codeblock` rule.
|
||||
*
|
||||
* @typedef {Object} ParseTreeCodeblockNode
|
||||
* @property {"codeblock"} rule
|
||||
* @property {"codeblock"} type
|
||||
* @property {number} start
|
||||
* @property {number} end
|
||||
* @property {Object} attributes
|
||||
* @property {ParseTreeAttribute} attributes.code
|
||||
* @property {ParseTreeAttribute} attributes.language
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
@ -27,6 +48,12 @@ exports.init = function(parser) {
|
||||
this.matchRegExp = /```([\w-]*)\r?\n/mg;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the code block and returns an array of `codeblock` widgets.
|
||||
*
|
||||
* @this {ThisRule}
|
||||
* @returns {ParseTreeCodeblockNode[]} An array containing a single codeblock widget object.
|
||||
*/
|
||||
exports.parse = function() {
|
||||
var reEnd = /(\r?\n```$)/mg;
|
||||
var languageStart = this.parser.pos + 3,
|
||||
|
@ -17,6 +17,34 @@ This is a widget invocation
|
||||
}}}
|
||||
|
||||
\*/
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {import("$:/core/modules/parsers/base.js").ParseTreeAttribute} ParseTreeAttribute
|
||||
* @typedef {import('../wikirulebase.js').WikiRuleBase} WikiRuleBase
|
||||
* @typedef {import('../../base.js').Parser} Parser
|
||||
* @typedef {typeof exports & WikiRuleBase & {nextTag?:ParseTreeHtmlNode;}} ThisRule
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the parser `html` rule
|
||||
*
|
||||
* @typedef {Object} ParseTreeHtmlNode
|
||||
* @property {"html"} rule
|
||||
* @property {"element"} type
|
||||
* @property {keyof HTMLElementTagNameMap} tag
|
||||
* @property {number} start
|
||||
* @property {number} end
|
||||
* @property {Record<string,ParseTreeAttribute>} attributes - Contains attributes of HTML element
|
||||
* @property {boolean} isSelfClosing - If tag is self-closing
|
||||
* @property {boolean} isBlock - If tag is a block element
|
||||
* @property {number} openTagStart
|
||||
* @property {number} openTagEnd
|
||||
* @property {number} closeTagStart
|
||||
* @property {number} closeTagEnd
|
||||
* @property {ParseTreeHtmlNode[]} [children]
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
@ -26,10 +54,18 @@ This is a widget invocation
|
||||
exports.name = "html";
|
||||
exports.types = {inline: true, block: true};
|
||||
|
||||
/**
|
||||
* @param {Parser} parser
|
||||
*/
|
||||
exports.init = function(parser) {
|
||||
this.parser = parser;
|
||||
};
|
||||
|
||||
/**
|
||||
* @this {ThisRule}
|
||||
* @param {number} startPos
|
||||
* @returns {number | undefined} Start position of next HTML tag
|
||||
*/
|
||||
exports.findNextMatch = function(startPos) {
|
||||
// Find the next tag
|
||||
this.nextTag = this.findNextTag(this.parser.source,startPos,{
|
||||
@ -38,11 +74,16 @@ exports.findNextMatch = function(startPos) {
|
||||
return this.nextTag ? this.nextTag.start : undefined;
|
||||
};
|
||||
|
||||
/*
|
||||
Parse the most recent match
|
||||
*/
|
||||
/**
|
||||
* Parse most recent match
|
||||
* @this {ThisRule}
|
||||
* @returns {ParseTreeHtmlNode[]} Array containing parsed HTML tag object
|
||||
*/
|
||||
exports.parse = function() {
|
||||
// Retrieve the most recent match so that recursive calls don't overwrite it
|
||||
/**
|
||||
* @type {ParseTreeHtmlNode}
|
||||
* Retrieve the most recent match so that recursive calls don't overwrite it
|
||||
*/
|
||||
var tag = this.nextTag;
|
||||
if (!tag.isSelfClosing) {
|
||||
tag.openTagStart = tag.start;
|
||||
@ -161,6 +202,15 @@ exports.parseTag = function(source,pos,options) {
|
||||
return node;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the next HTML tag in the source
|
||||
*
|
||||
* @this {ThisRule}
|
||||
* @param {string} source
|
||||
* @param {number} pos - Position to start searching from
|
||||
* @param {Object} options
|
||||
* @returns {Object|null} Parsed tag object or null if no valid tag is found
|
||||
*/
|
||||
exports.findNextTag = function(source,pos,options) {
|
||||
// A regexp for finding candidate HTML tags
|
||||
var reLookahead = /<([a-zA-Z\-\$\.]+)/g;
|
||||
|
@ -25,16 +25,18 @@ Attributes are stored as hashmaps of the following objects:
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
type: content type of text
|
||||
text: text to be parsed
|
||||
options: see below:
|
||||
parseAsInline: true to parse text as inline instead of block
|
||||
wiki: reference to wiki to use
|
||||
_canonical_uri: optional URI of content if text is missing or empty
|
||||
configTrimWhiteSpace: true to trim whitespace
|
||||
*/
|
||||
var WikiParser = function(type,text,options) {
|
||||
/**
|
||||
* @typedef {import('../base').Parser} Parser
|
||||
*/
|
||||
|
||||
/**
|
||||
* WikiParser class for parsing text of a specified MIME type.
|
||||
*
|
||||
* @class
|
||||
* @extends {Parser}
|
||||
* @constructor
|
||||
*/
|
||||
function WikiParser(type,text,options) {
|
||||
this.wiki = options.wiki;
|
||||
var self = this;
|
||||
// Check for an externally linked tiddler
|
||||
@ -99,8 +101,11 @@ var WikiParser = function(type,text,options) {
|
||||
// Return the parse tree
|
||||
};
|
||||
|
||||
/*
|
||||
*/
|
||||
/**
|
||||
* Load a remote tiddler from a given URL.
|
||||
*
|
||||
* @param {string} url - The URL of the remote tiddler to load.
|
||||
*/
|
||||
WikiParser.prototype.loadRemoteTiddler = function(url) {
|
||||
var self = this;
|
||||
$tw.utils.httpRequest({
|
||||
|
@ -12,22 +12,46 @@ Base class for wiki parser rules
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
This constructor is always overridden with a blank constructor, and so shouldn't be used
|
||||
*/
|
||||
var WikiRuleBase = function() {
|
||||
/**
|
||||
* @typedef {import('../base').Parser} Parser
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for wiki rules.
|
||||
* This constructor is always overridden with a blank constructor, and so shouldn't be used
|
||||
*
|
||||
* @class
|
||||
* @constructor
|
||||
*/
|
||||
function WikiRuleBase() {
|
||||
/**
|
||||
* Inject by parser
|
||||
* @type {Record<"pragma"|"block"|"inline", boolean>}
|
||||
*/
|
||||
this.is = {};
|
||||
/**
|
||||
* @type {RegExp}
|
||||
*/
|
||||
this.matchRegExp;
|
||||
};
|
||||
|
||||
/*
|
||||
To be overridden by individual rules
|
||||
*/
|
||||
/**
|
||||
* Initialize rule with given parser instance
|
||||
* To be overridden by individual rules
|
||||
*
|
||||
* @param {Parser} parser - Parser instance to initialize with
|
||||
*/
|
||||
WikiRuleBase.prototype.init = function(parser) {
|
||||
this.parser = parser;
|
||||
};
|
||||
|
||||
/*
|
||||
Default implementation of findNextMatch uses RegExp matching
|
||||
*/
|
||||
/**
|
||||
* Default implementation of findNextMatch uses RegExp matching
|
||||
* Find next match in source starting from given position using RegExp matching
|
||||
*
|
||||
* @param {number} startPos - Position to start searching from
|
||||
* @returns {number|undefined} Index of next match or undefined if no match is found
|
||||
*/
|
||||
WikiRuleBase.prototype.findNextMatch = function(startPos) {
|
||||
this.matchRegExp.lastIndex = startPos;
|
||||
this.match = this.matchRegExp.exec(this.parser.source);
|
||||
|
@ -12,22 +12,30 @@ Widget base class
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
Create a widget object for a parse tree node
|
||||
parseTreeNode: reference to the parse tree node to be rendered
|
||||
options: see below
|
||||
Options include:
|
||||
wiki: mandatory reference to wiki associated with this render tree
|
||||
parentWidget: optional reference to a parent renderer node for the context chain
|
||||
document: optional document object to use instead of global document
|
||||
*/
|
||||
var Widget = function(parseTreeNode,options) {
|
||||
/**
|
||||
* Widget class for creating a widget object for a parse tree node.
|
||||
*
|
||||
* @class
|
||||
* @constructor
|
||||
* @param {Object} parseTreeNode - Reference to the parse tree node to be rendered.
|
||||
* @param {Object} options - Options for the widget.
|
||||
* @param {Object} options.wiki - Mandatory reference to the wiki associated with this render tree.
|
||||
* @param {Widget} [options.parentWidget] - Optional reference to a parent renderer node for the context chain.
|
||||
* @param {Document} [options.document] - Optional document object to use instead of the global document.
|
||||
*/
|
||||
function Widget(parseTreeNode,options) {
|
||||
this.initialise(parseTreeNode,options);
|
||||
};
|
||||
|
||||
/*
|
||||
Initialise widget properties. These steps are pulled out of the constructor so that we can reuse them in subclasses
|
||||
*/
|
||||
/**
|
||||
* Initialise widget properties. These steps are pulled out of the constructor so that we can reuse them in subclasses.
|
||||
*
|
||||
* @param {Object} parseTreeNode - Reference to the parse tree node to be rendered.
|
||||
* @param {Object} options - Options for the widget.
|
||||
* @param {Object} options.wiki - Mandatory reference to the wiki associated with this render tree.
|
||||
* @param {Widget} [options.parentWidget] - Optional reference to a parent renderer node for the context chain.
|
||||
* @param {Document} [options.document] - Optional document object to use instead of the global document.
|
||||
*/
|
||||
Widget.prototype.initialise = function(parseTreeNode,options) {
|
||||
// Bail if parseTreeNode is undefined, meaning that the widget constructor was called without any arguments so that it can be subclassed
|
||||
if(parseTreeNode === undefined) {
|
||||
@ -64,9 +72,12 @@ Widget.prototype.initialise = function(parseTreeNode,options) {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Render this widget into the DOM
|
||||
*/
|
||||
/**
|
||||
* Render this widget into the DOM.
|
||||
*
|
||||
* @param {Element} parent - The parent DOM node to render into.
|
||||
* @param {Element} nextSibling - The next sibling DOM node to render before.
|
||||
*/
|
||||
Widget.prototype.render = function(parent,nextSibling) {
|
||||
this.parentDomNode = parent;
|
||||
this.execute();
|
||||
|
@ -22,7 +22,7 @@ Adds the following properties to the wiki object:
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var widget = require("$:/core/modules/widgets/widget.js");
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
|
||||
var USER_NAME_TITLE = "$:/status/UserName",
|
||||
TIMESTAMP_DISABLE_TITLE = "$:/config/TimestampDisable";
|
||||
@ -1050,19 +1050,29 @@ exports.initParsers = function(moduleType) {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Parse a block of text of a specified MIME type
|
||||
type: content type of text to be parsed
|
||||
text: text
|
||||
options: see below
|
||||
Options include:
|
||||
parseAsInline: if true, the text of the tiddler will be parsed as an inline run
|
||||
_canonical_uri: optional string of the canonical URI of this content
|
||||
*/
|
||||
/**
|
||||
* @typedef {import('$:/core/modules/parsers/base.js').Parser} Parser
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse a block of text of a specified MIME type
|
||||
*
|
||||
* @param {string} type - Content type of text to be parsed
|
||||
* @param {string} text - Text to be parsed
|
||||
* @param {Object} [options] - Options for parsing
|
||||
* @param {boolean} [options.parseAsInline=false] - If true, text will be parsed as an inline run
|
||||
* @param {string} [options._canonical_uri] - Optional string of canonical URI of this content
|
||||
* @param {string} [options.defaultType="text/vnd.tiddlywiki"] - Default type to use if no parser is found for specified type
|
||||
* @param {boolean} [options.configTrimWhiteSpace=false] - If true, trims white space according to configuration
|
||||
*
|
||||
* @returns {Parser | null} Parser instance or null if no parser is found
|
||||
*/
|
||||
exports.parseText = function(type,text,options) {
|
||||
text = text || "";
|
||||
options = options || {};
|
||||
// Select a parser
|
||||
/**
|
||||
* Select a parser
|
||||
*/
|
||||
var Parser = $tw.Wiki.parsers[type];
|
||||
if(!Parser && $tw.utils.getFileExtensionInfo(type)) {
|
||||
Parser = $tw.Wiki.parsers[$tw.utils.getFileExtensionInfo(type).type];
|
||||
@ -1153,14 +1163,16 @@ exports.getTextReferenceParserInfo = function(title,field,index,options) {
|
||||
return parserInfo;
|
||||
}
|
||||
|
||||
/*
|
||||
Parse a block of text of a specified MIME type
|
||||
text: text on which to perform substitutions
|
||||
widget
|
||||
options: see below
|
||||
Options include:
|
||||
substitutions: an optional array of substitutions
|
||||
*/
|
||||
/**
|
||||
* Parse a block of text of a specified MIME type and perform substitutions.
|
||||
*
|
||||
* @param {string} text - The text on which to perform substitutions.
|
||||
* @param {Widget} widget - The widget context used for variable substitution.
|
||||
* @param {Object} [options] - Options for substitutions.
|
||||
* @param {Array<{name: string, value: string}>} [options.substitutions] - An optional array of substitutions.
|
||||
*
|
||||
* @returns {string} The text with substitutions applied.
|
||||
*/
|
||||
exports.getSubstitutedText = function(text,widget,options) {
|
||||
options = options || {};
|
||||
text = text || "";
|
||||
@ -1181,15 +1193,17 @@ exports.getSubstitutedText = function(text,widget,options) {
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Make a widget tree for a parse tree
|
||||
parser: parser object
|
||||
options: see below
|
||||
Options include:
|
||||
document: optional document to use
|
||||
variables: hashmap of variables to set
|
||||
parentWidget: optional parent widget for the root node
|
||||
*/
|
||||
/**
|
||||
* Create a widget tree for a parse tree.
|
||||
*
|
||||
* @param {Object} parser - The parser object containing the parse tree.
|
||||
* @param {Object} [options] - Options for creating the widget tree.
|
||||
* @param {Document} [options.document] - Optional document to use.
|
||||
* @param {Object} [options.variables] - Hashmap of variables to set.
|
||||
* @param {Widget} [options.parentWidget] - Optional parent widget for the root node.
|
||||
*
|
||||
* @returns {Widget} The root widget of the created widget tree.
|
||||
*/
|
||||
exports.makeWidget = function(parser,options) {
|
||||
options = options || {};
|
||||
var widgetNode = {
|
||||
@ -1214,7 +1228,7 @@ exports.makeWidget = function(parser,options) {
|
||||
// Add in the supplied parse tree nodes
|
||||
currWidgetNode.children = parser ? parser.tree : [];
|
||||
// Create the widget
|
||||
return new widget.widget(widgetNode,{
|
||||
return new Widget(widgetNode,{
|
||||
wiki: this,
|
||||
document: options.document || $tw.fakeDocument,
|
||||
parentWidget: options.parentWidget
|
||||
@ -1497,9 +1511,13 @@ exports.search = function(text,options) {
|
||||
return results;
|
||||
};
|
||||
|
||||
/*
|
||||
Trigger a load for a tiddler if it is skinny. Returns the text, or undefined if the tiddler is missing, null if the tiddler is being lazily loaded.
|
||||
*/
|
||||
/**
|
||||
* Trigger a load for a tiddler if it is skinny. Returns the text, or undefined if the tiddler is missing, null if the tiddler is being lazily loaded.
|
||||
*
|
||||
* @param {string} title - The title of the tiddler.
|
||||
* @param {string} [defaultText] - The default text to return if the tiddler is missing.
|
||||
* @returns {string | null | undefined} - The text of the tiddler, undefined if the tiddler is missing, or null if the tiddler is being lazily loaded.
|
||||
*/
|
||||
exports.getTiddlerText = function(title,defaultText) {
|
||||
var tiddler = this.getTiddler(title);
|
||||
// Return undefined if the tiddler isn't found
|
||||
|
@ -13,6 +13,7 @@
|
||||
"bin": {
|
||||
"tiddlywiki": "./tiddlywiki.js"
|
||||
},
|
||||
"types": "./types/tw.d.ts",
|
||||
"main": "./boot/boot.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
// Allow `import('$:/core/modules/...')` instead of `import('../../core/modules/...')`. Only works inside this project.
|
||||
"$:/core/*": ["core/*"]
|
||||
},
|
||||
"baseUrl": ".",
|
||||
"rootDir": ".",
|
||||
"noImplicitAny": false,
|
||||
"strict": false,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "types/",
|
||||
"declarationMap": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "Node16",
|
||||
"outDir": "types/",
|
||||
"skipLibCheck": true,
|
||||
"target": "ESNext"
|
||||
},
|
||||
"include": ["./core/**/*.js", "./core/**/*.d.ts", "types/*.d.ts"],
|
||||
// Exclude the generated types from the core folder
|
||||
"exclude": ["types/core",]
|
||||
}
|
6
types/ast.d.ts
vendored
Normal file
6
types/ast.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
import { ParseTreeCodeblockNode } from '$:/core/modules/parsers/wikiparser/rules/codeblock.js';
|
||||
export { ParseTreeCodeblockNode } from '$:/core/modules/parsers/wikiparser/rules/codeblock.js';
|
||||
import { ParseTreeHtmlNode } from '$:/core/modules/parsers/wikiparser/rules/html.js';
|
||||
export { ParseTreeHtmlNode } from '$:/core/modules/parsers/wikiparser/rules/html.js';
|
||||
|
||||
export type WikiASTNode = ParseTreeCodeblockNode | ParseTreeHtmlNode;
|
19
types/tsconfig.json
Normal file
19
types/tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
// Allow `import('$:/core/modules/...')` instead of `import('../../core/modules/...')`. Only works inside this project.
|
||||
"$:/core/*": ["core/*"]
|
||||
},
|
||||
"baseUrl": "./",
|
||||
"rootDir": "./",
|
||||
"noEmit": true,
|
||||
"noImplicitAny": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"declaration": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "Node16",
|
||||
"skipLibCheck": true,
|
||||
"target": "ESNext"
|
||||
},
|
||||
"include": ["./core/**/*.d.ts"]
|
||||
}
|
7
types/tw.d.ts
vendored
Normal file
7
types/tw.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import * as Wiki from '$:/core/modules/wiki';
|
||||
|
||||
declare global {
|
||||
var $tw: {
|
||||
wiki: typeof Wiki;
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user