1
0
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:
lin onetwo 2025-03-10 19:04:56 +09:00 committed by GitHub
commit 927291b74f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 330 additions and 74 deletions

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

@ -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

@ -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

@ -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

@ -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

@ -0,0 +1,7 @@
import * as Wiki from '$:/core/modules/wiki';
declare global {
var $tw: {
wiki: typeof Wiki;
};
}