2013-10-12 16:05:13 +00:00
|
|
|
/*\
|
2013-11-08 08:47:00 +00:00
|
|
|
title: $:/core/modules/widgets/list.js
|
2013-10-12 16:05:13 +00:00
|
|
|
type: application/javascript
|
2013-11-08 08:47:00 +00:00
|
|
|
module-type: widget
|
2013-10-12 16:05:13 +00:00
|
|
|
|
|
|
|
List and list item widgets
|
|
|
|
|
|
|
|
\*/
|
|
|
|
(function(){
|
|
|
|
|
|
|
|
/*jslint node: true, browser: true */
|
|
|
|
/*global $tw: false */
|
|
|
|
"use strict";
|
|
|
|
|
2013-11-08 08:47:00 +00:00
|
|
|
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
2013-10-12 16:05:13 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
The list widget creates list element sub-widgets that reach back into the list widget for their configuration
|
|
|
|
*/
|
|
|
|
|
|
|
|
var ListWidget = function(parseTreeNode,options) {
|
2013-11-04 22:22:28 +00:00
|
|
|
// Main initialisation inherited from widget.js
|
2013-10-12 16:05:13 +00:00
|
|
|
this.initialise(parseTreeNode,options);
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Inherit from the base widget class
|
|
|
|
*/
|
|
|
|
ListWidget.prototype = new Widget();
|
|
|
|
|
Alternate fix for inconsistent list template syntax (#7827)
* Alternate fix for inconsistent list template syntax
First attempt, which fails on the ListWidget/WithMissingTemplate test.
* Make WithMissingTemplate test pass, inefficiently
Unfortunately, this ends up being very inefficient, because the
clone-and-mutate logic is repeated for every list item. Not ideal.
* More efficient way to do it
This also makes the failing test pass, but far more efficiently.
* Improve performance of list template discovery
Since parse tree nodes never change after widget creation (whereas
attribute values *can* change), we can safely search for the explicit
list templtaes only once, at widget creation time. This saves time as
the search doesn't have to be done on each re-render, and also allows us
to safely do a clone-and-mutate step to extract the list widget's body
(if any) without any `$list-empty` or other items. That, in turn, allows
using the list widget's body as the template even if `$list-empty` is
specified inside the widget body.
2023-11-06 21:18:31 +00:00
|
|
|
ListWidget.prototype.initialise = function(parseTreeNode,options) {
|
|
|
|
// Bail if parseTreeNode is undefined, meaning that the ListWidget constructor was called without any arguments so that it can be subclassed
|
|
|
|
if(parseTreeNode === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// First call parent constructor to set everything else up
|
|
|
|
Widget.prototype.initialise.call(this,parseTreeNode,options);
|
|
|
|
// Now look for <$list-template> and <$list-empty> widgets as immediate child widgets
|
|
|
|
// This is safe to do during initialization because parse trees never change after creation
|
|
|
|
this.findExplicitTemplates();
|
|
|
|
}
|
|
|
|
|
2013-10-12 16:05:13 +00:00
|
|
|
/*
|
|
|
|
Render this widget into the DOM
|
|
|
|
*/
|
|
|
|
ListWidget.prototype.render = function(parent,nextSibling) {
|
2021-01-09 13:25:48 +00:00
|
|
|
// Initialise the storyviews if they've not been done already
|
|
|
|
if(!this.storyViews) {
|
|
|
|
ListWidget.prototype.storyViews = {};
|
|
|
|
$tw.modules.applyMethods("storyview",this.storyViews);
|
|
|
|
}
|
2013-10-12 16:05:13 +00:00
|
|
|
this.parentDomNode = parent;
|
2023-11-25 09:35:05 +00:00
|
|
|
var changedAttributes = this.computeAttributes();
|
|
|
|
this.execute(changedAttributes);
|
2013-10-12 16:05:13 +00:00
|
|
|
this.renderChildren(parent,nextSibling);
|
2013-11-07 22:55:39 +00:00
|
|
|
// Construct the storyview
|
|
|
|
var StoryView = this.storyViews[this.storyViewName];
|
2016-08-27 13:23:49 +00:00
|
|
|
if(this.storyViewName && !StoryView) {
|
2016-08-26 10:34:43 +00:00
|
|
|
StoryView = this.storyViews["classic"];
|
|
|
|
}
|
2014-03-10 18:41:11 +00:00
|
|
|
if(StoryView && !this.document.isTiddlyWikiFakeDom) {
|
2014-08-30 19:44:26 +00:00
|
|
|
this.storyview = new StoryView(this);
|
2014-03-10 18:41:11 +00:00
|
|
|
} else {
|
|
|
|
this.storyview = null;
|
|
|
|
}
|
2022-01-19 19:48:02 +00:00
|
|
|
if(this.storyview && this.storyview.renderEnd) {
|
|
|
|
this.storyview.renderEnd();
|
|
|
|
}
|
2013-10-12 16:05:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Compute the internal state of the widget
|
|
|
|
*/
|
2023-11-25 09:35:05 +00:00
|
|
|
ListWidget.prototype.execute = function(changedAttributes) {
|
2023-10-14 08:31:11 +00:00
|
|
|
var self = this;
|
2013-10-12 16:05:13 +00:00
|
|
|
// Get our attributes
|
|
|
|
this.template = this.getAttribute("template");
|
|
|
|
this.editTemplate = this.getAttribute("editTemplate");
|
2013-11-02 10:12:55 +00:00
|
|
|
this.variableName = this.getAttribute("variable","currentTiddler");
|
2021-04-26 13:41:26 +00:00
|
|
|
this.counterName = this.getAttribute("counter");
|
2013-11-04 22:22:28 +00:00
|
|
|
this.storyViewName = this.getAttribute("storyview");
|
|
|
|
this.historyTitle = this.getAttribute("history");
|
2023-11-25 09:35:05 +00:00
|
|
|
// Create join template only if needed
|
|
|
|
if(this.join === undefined || (changedAttributes && changedAttributes.join)) {
|
|
|
|
this.join = this.makeJoinTemplate();
|
|
|
|
}
|
2013-10-12 16:05:13 +00:00
|
|
|
// Compose the list elements
|
|
|
|
this.list = this.getTiddlerList();
|
|
|
|
var members = [],
|
2015-02-13 22:42:36 +00:00
|
|
|
self = this;
|
2013-10-12 16:05:13 +00:00
|
|
|
// Check for an empty list
|
2015-02-13 22:42:36 +00:00
|
|
|
if(this.list.length === 0) {
|
2013-10-12 16:05:13 +00:00
|
|
|
members = this.getEmptyMessage();
|
|
|
|
} else {
|
|
|
|
$tw.utils.each(this.list,function(title,index) {
|
2021-04-20 08:15:11 +00:00
|
|
|
members.push(self.makeItemTemplate(title,index));
|
2013-10-12 16:05:13 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
// Construct the child widgets
|
|
|
|
this.makeChildWidgets(members);
|
2013-11-04 22:22:28 +00:00
|
|
|
// Clear the last history
|
|
|
|
this.history = [];
|
2013-10-12 16:05:13 +00:00
|
|
|
};
|
|
|
|
|
2023-10-14 08:31:11 +00:00
|
|
|
ListWidget.prototype.findExplicitTemplates = function() {
|
|
|
|
var self = this;
|
|
|
|
this.explicitListTemplate = null;
|
|
|
|
this.explicitEmptyTemplate = null;
|
2023-11-25 09:35:05 +00:00
|
|
|
this.explicitJoinTemplate = null;
|
Alternate fix for inconsistent list template syntax (#7827)
* Alternate fix for inconsistent list template syntax
First attempt, which fails on the ListWidget/WithMissingTemplate test.
* Make WithMissingTemplate test pass, inefficiently
Unfortunately, this ends up being very inefficient, because the
clone-and-mutate logic is repeated for every list item. Not ideal.
* More efficient way to do it
This also makes the failing test pass, but far more efficiently.
* Improve performance of list template discovery
Since parse tree nodes never change after widget creation (whereas
attribute values *can* change), we can safely search for the explicit
list templtaes only once, at widget creation time. This saves time as
the search doesn't have to be done on each re-render, and also allows us
to safely do a clone-and-mutate step to extract the list widget's body
(if any) without any `$list-empty` or other items. That, in turn, allows
using the list widget's body as the template even if `$list-empty` is
specified inside the widget body.
2023-11-06 21:18:31 +00:00
|
|
|
this.hasTemplateInBody = false;
|
2023-10-14 08:31:11 +00:00
|
|
|
var searchChildren = function(childNodes) {
|
2023-12-21 10:35:40 +00:00
|
|
|
var foundInlineTemplate = false;
|
2023-10-14 08:31:11 +00:00
|
|
|
$tw.utils.each(childNodes,function(node) {
|
|
|
|
if(node.type === "list-template") {
|
|
|
|
self.explicitListTemplate = node.children;
|
|
|
|
} else if(node.type === "list-empty") {
|
|
|
|
self.explicitEmptyTemplate = node.children;
|
2023-11-25 09:35:05 +00:00
|
|
|
} else if(node.type === "list-join") {
|
|
|
|
self.explicitJoinTemplate = node.children;
|
2023-10-14 08:31:11 +00:00
|
|
|
} else if(node.type === "element" && node.tag === "p") {
|
|
|
|
searchChildren(node.children);
|
2023-12-21 10:35:40 +00:00
|
|
|
foundInlineTemplate = true;
|
Alternate fix for inconsistent list template syntax (#7827)
* Alternate fix for inconsistent list template syntax
First attempt, which fails on the ListWidget/WithMissingTemplate test.
* Make WithMissingTemplate test pass, inefficiently
Unfortunately, this ends up being very inefficient, because the
clone-and-mutate logic is repeated for every list item. Not ideal.
* More efficient way to do it
This also makes the failing test pass, but far more efficiently.
* Improve performance of list template discovery
Since parse tree nodes never change after widget creation (whereas
attribute values *can* change), we can safely search for the explicit
list templtaes only once, at widget creation time. This saves time as
the search doesn't have to be done on each re-render, and also allows us
to safely do a clone-and-mutate step to extract the list widget's body
(if any) without any `$list-empty` or other items. That, in turn, allows
using the list widget's body as the template even if `$list-empty` is
specified inside the widget body.
2023-11-06 21:18:31 +00:00
|
|
|
} else {
|
2023-12-21 10:35:40 +00:00
|
|
|
foundInlineTemplate = true;
|
2023-10-14 08:31:11 +00:00
|
|
|
}
|
|
|
|
});
|
2023-12-21 10:35:40 +00:00
|
|
|
return foundInlineTemplate;
|
2023-10-14 08:31:11 +00:00
|
|
|
};
|
2023-12-21 10:35:40 +00:00
|
|
|
this.hasTemplateInBody = searchChildren(this.parseTreeNode.children);
|
2023-10-14 08:31:11 +00:00
|
|
|
}
|
|
|
|
|
2013-10-12 16:05:13 +00:00
|
|
|
ListWidget.prototype.getTiddlerList = function() {
|
2023-10-14 08:31:11 +00:00
|
|
|
var limit = $tw.utils.getInt(this.getAttribute("limit",""),undefined);
|
2013-10-12 16:05:13 +00:00
|
|
|
var defaultFilter = "[!is[system]sort[title]]";
|
2023-10-14 08:31:11 +00:00
|
|
|
var results = this.wiki.filterTiddlers(this.getAttribute("filter",defaultFilter),this);
|
|
|
|
if(limit !== undefined) {
|
|
|
|
if(limit >= 0) {
|
|
|
|
results = results.slice(0,limit);
|
|
|
|
} else {
|
|
|
|
results = results.slice(limit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return results;
|
2013-10-12 16:05:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
ListWidget.prototype.getEmptyMessage = function() {
|
Init less parsers (#4954)
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* Don't override browser selection colours by default
Reverts some of #4590
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* list-widget, init new Parser() only if needed.
* fix braces
Co-authored-by: jeremy@jermolene.com <jeremy@jermolene.com>
2020-11-01 17:14:42 +00:00
|
|
|
var parser,
|
2023-10-14 08:31:11 +00:00
|
|
|
emptyMessage = this.getAttribute("emptyMessage");
|
|
|
|
// If emptyMessage attribute is not present or empty then look for an explicit empty template
|
|
|
|
if(!emptyMessage) {
|
|
|
|
if(this.explicitEmptyTemplate) {
|
|
|
|
return this.explicitEmptyTemplate;
|
|
|
|
} else {
|
|
|
|
return [];
|
|
|
|
}
|
Init less parsers (#4954)
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* Don't override browser selection colours by default
Reverts some of #4590
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* add a new-line before the log text to increase readability of the test output
* make eslint, jslint happy
* it shouldn't be there
* fremove this file from my PRs
* list-widget, init new Parser() only if needed.
* fix braces
Co-authored-by: jeremy@jermolene.com <jeremy@jermolene.com>
2020-11-01 17:14:42 +00:00
|
|
|
}
|
|
|
|
parser = this.wiki.parseText("text/vnd.tiddlywiki",emptyMessage,{parseAsInline: true});
|
2013-10-12 16:05:13 +00:00
|
|
|
if(parser) {
|
|
|
|
return parser.tree;
|
|
|
|
} else {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-11-25 09:35:05 +00:00
|
|
|
/*
|
|
|
|
Compose the template for a join between list items
|
|
|
|
*/
|
|
|
|
ListWidget.prototype.makeJoinTemplate = function() {
|
|
|
|
var parser,
|
|
|
|
join = this.getAttribute("join","");
|
|
|
|
if(join) {
|
|
|
|
parser = this.wiki.parseText("text/vnd.tiddlywiki",join,{parseAsInline:true})
|
|
|
|
if(parser) {
|
|
|
|
return parser.tree;
|
|
|
|
} else {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return this.explicitJoinTemplate; // May be null, and that's fine
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-10-12 16:05:13 +00:00
|
|
|
/*
|
|
|
|
Compose the template for a list item
|
|
|
|
*/
|
2021-04-20 08:15:11 +00:00
|
|
|
ListWidget.prototype.makeItemTemplate = function(title,index) {
|
2013-10-12 16:05:13 +00:00
|
|
|
// Check if the tiddler is a draft
|
|
|
|
var tiddler = this.wiki.getTiddler(title),
|
|
|
|
isDraft = tiddler && tiddler.hasField("draft.of"),
|
|
|
|
template = this.template,
|
2023-11-25 09:35:05 +00:00
|
|
|
join = this.join,
|
2013-10-12 16:05:13 +00:00
|
|
|
templateTree;
|
|
|
|
if(isDraft && this.editTemplate) {
|
|
|
|
template = this.editTemplate;
|
|
|
|
}
|
|
|
|
// Compose the transclusion of the template
|
2013-11-02 10:12:55 +00:00
|
|
|
if(template) {
|
|
|
|
templateTree = [{type: "transclude", attributes: {tiddler: {type: "string", value: template}}}];
|
2013-10-12 16:05:13 +00:00
|
|
|
} else {
|
2023-10-14 08:31:11 +00:00
|
|
|
// Check for child nodes of the list widget
|
2013-11-02 10:12:55 +00:00
|
|
|
if(this.parseTreeNode.children && this.parseTreeNode.children.length > 0) {
|
2023-10-14 08:31:11 +00:00
|
|
|
// Check for a <$list-item> widget
|
|
|
|
if(this.explicitListTemplate) {
|
|
|
|
templateTree = this.explicitListTemplate;
|
Alternate fix for inconsistent list template syntax (#7827)
* Alternate fix for inconsistent list template syntax
First attempt, which fails on the ListWidget/WithMissingTemplate test.
* Make WithMissingTemplate test pass, inefficiently
Unfortunately, this ends up being very inefficient, because the
clone-and-mutate logic is repeated for every list item. Not ideal.
* More efficient way to do it
This also makes the failing test pass, but far more efficiently.
* Improve performance of list template discovery
Since parse tree nodes never change after widget creation (whereas
attribute values *can* change), we can safely search for the explicit
list templtaes only once, at widget creation time. This saves time as
the search doesn't have to be done on each re-render, and also allows us
to safely do a clone-and-mutate step to extract the list widget's body
(if any) without any `$list-empty` or other items. That, in turn, allows
using the list widget's body as the template even if `$list-empty` is
specified inside the widget body.
2023-11-06 21:18:31 +00:00
|
|
|
} else if(this.hasTemplateInBody) {
|
2023-10-14 08:31:11 +00:00
|
|
|
templateTree = this.parseTreeNode.children;
|
|
|
|
}
|
|
|
|
}
|
Alternate fix for inconsistent list template syntax (#7827)
* Alternate fix for inconsistent list template syntax
First attempt, which fails on the ListWidget/WithMissingTemplate test.
* Make WithMissingTemplate test pass, inefficiently
Unfortunately, this ends up being very inefficient, because the
clone-and-mutate logic is repeated for every list item. Not ideal.
* More efficient way to do it
This also makes the failing test pass, but far more efficiently.
* Improve performance of list template discovery
Since parse tree nodes never change after widget creation (whereas
attribute values *can* change), we can safely search for the explicit
list templtaes only once, at widget creation time. This saves time as
the search doesn't have to be done on each re-render, and also allows us
to safely do a clone-and-mutate step to extract the list widget's body
(if any) without any `$list-empty` or other items. That, in turn, allows
using the list widget's body as the template even if `$list-empty` is
specified inside the widget body.
2023-11-06 21:18:31 +00:00
|
|
|
if(!templateTree || templateTree.length === 0) {
|
2013-11-02 10:12:55 +00:00
|
|
|
// Default template is a link to the title
|
|
|
|
templateTree = [{type: "element", tag: this.parseTreeNode.isBlock ? "div" : "span", children: [{type: "link", attributes: {to: {type: "string", value: title}}, children: [
|
2023-10-14 08:31:11 +00:00
|
|
|
{type: "text", text: title}
|
2013-11-02 10:12:55 +00:00
|
|
|
]}]}];
|
2013-10-14 16:35:52 +00:00
|
|
|
}
|
2013-10-12 16:05:13 +00:00
|
|
|
}
|
|
|
|
// Return the list item
|
2023-11-25 09:35:05 +00:00
|
|
|
var parseTreeNode = {type: "listitem", itemTitle: title, variableName: this.variableName, children: templateTree, join: join};
|
|
|
|
parseTreeNode.isLast = index === this.list.length - 1;
|
2021-04-26 13:41:26 +00:00
|
|
|
if(this.counterName) {
|
|
|
|
parseTreeNode.counter = (index + 1).toString();
|
|
|
|
parseTreeNode.counterName = this.counterName;
|
2021-04-20 08:15:11 +00:00
|
|
|
parseTreeNode.isFirst = index === 0;
|
|
|
|
}
|
|
|
|
return parseTreeNode;
|
2013-10-12 16:05:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
|
|
|
|
*/
|
|
|
|
ListWidget.prototype.refresh = function(changedTiddlers) {
|
2015-02-23 10:24:03 +00:00
|
|
|
var changedAttributes = this.computeAttributes(),
|
|
|
|
result;
|
|
|
|
// Call the storyview
|
|
|
|
if(this.storyview && this.storyview.refreshStart) {
|
|
|
|
this.storyview.refreshStart(changedTiddlers,changedAttributes);
|
|
|
|
}
|
2013-10-12 16:05:13 +00:00
|
|
|
// Completely refresh if any of our attributes have changed
|
2023-11-25 09:35:05 +00:00
|
|
|
if(changedAttributes.filter || changedAttributes.variable || changedAttributes.counter || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.join || changedAttributes.emptyMessage || changedAttributes.storyview || changedAttributes.history) {
|
2013-10-12 16:05:13 +00:00
|
|
|
this.refreshSelf();
|
2015-02-23 10:24:03 +00:00
|
|
|
result = true;
|
2013-10-12 16:05:13 +00:00
|
|
|
} else {
|
|
|
|
// Handle any changes to the list
|
2015-02-23 10:24:03 +00:00
|
|
|
result = this.handleListChanges(changedTiddlers);
|
2013-11-04 22:22:28 +00:00
|
|
|
// Handle any changes to the history stack
|
|
|
|
if(this.historyTitle && changedTiddlers[this.historyTitle]) {
|
|
|
|
this.handleHistoryChanges();
|
|
|
|
}
|
|
|
|
}
|
2015-02-23 10:24:03 +00:00
|
|
|
// Call the storyview
|
|
|
|
if(this.storyview && this.storyview.refreshEnd) {
|
|
|
|
this.storyview.refreshEnd(changedTiddlers,changedAttributes);
|
|
|
|
}
|
|
|
|
return result;
|
2013-11-04 22:22:28 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Handle any changes to the history list
|
|
|
|
*/
|
|
|
|
ListWidget.prototype.handleHistoryChanges = function() {
|
|
|
|
// Get the history data
|
2015-07-10 15:43:50 +00:00
|
|
|
var newHistory = this.wiki.getTiddlerDataCached(this.historyTitle,[]);
|
2013-11-04 22:22:28 +00:00
|
|
|
// Ignore any entries of the history that match the previous history
|
|
|
|
var entry = 0;
|
|
|
|
while(entry < newHistory.length && entry < this.history.length && newHistory[entry].title === this.history[entry].title) {
|
|
|
|
entry++;
|
|
|
|
}
|
|
|
|
// Navigate forwards to each of the new tiddlers
|
|
|
|
while(entry < newHistory.length) {
|
|
|
|
if(this.storyview && this.storyview.navigateTo) {
|
|
|
|
this.storyview.navigateTo(newHistory[entry]);
|
|
|
|
}
|
|
|
|
entry++;
|
2013-10-12 16:05:13 +00:00
|
|
|
}
|
2013-11-04 22:22:28 +00:00
|
|
|
// Update the history
|
|
|
|
this.history = newHistory;
|
2013-10-12 16:05:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Process any changes to the list
|
|
|
|
*/
|
|
|
|
ListWidget.prototype.handleListChanges = function(changedTiddlers) {
|
|
|
|
// Get the new list
|
|
|
|
var prevList = this.list;
|
|
|
|
this.list = this.getTiddlerList();
|
|
|
|
// Check for an empty list
|
|
|
|
if(this.list.length === 0) {
|
|
|
|
// Check if it was empty before
|
|
|
|
if(prevList.length === 0) {
|
|
|
|
// If so, just refresh the empty message
|
|
|
|
return this.refreshChildren(changedTiddlers);
|
|
|
|
} else {
|
|
|
|
// Replace the previous content with the empty message
|
2013-11-05 08:52:17 +00:00
|
|
|
for(t=this.children.length-1; t>=0; t--) {
|
|
|
|
this.removeListItem(t);
|
|
|
|
}
|
2013-10-22 17:14:16 +00:00
|
|
|
var nextSibling = this.findNextSiblingDomNode();
|
2013-10-12 16:05:13 +00:00
|
|
|
this.makeChildWidgets(this.getEmptyMessage());
|
|
|
|
this.renderChildren(this.parentDomNode,nextSibling);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// If the list was empty then we need to remove the empty message
|
|
|
|
if(prevList.length === 0) {
|
2023-05-18 16:27:05 +00:00
|
|
|
this.removeChildDomNodes();
|
2013-10-12 16:05:13 +00:00
|
|
|
this.children = [];
|
|
|
|
}
|
2021-04-26 13:41:26 +00:00
|
|
|
// If we are providing an counter variable then we must refresh the items, otherwise we can rearrange them
|
2021-04-20 08:15:11 +00:00
|
|
|
var hasRefreshed = false,t;
|
2021-04-26 13:41:26 +00:00
|
|
|
if(this.counterName) {
|
2023-09-24 19:19:50 +00:00
|
|
|
var mustRefreshOldLast = false;
|
|
|
|
var oldLength = this.children.length;
|
2021-04-20 08:15:11 +00:00
|
|
|
// Cycle through the list and remove and re-insert the first item that has changed, and all the remaining items
|
|
|
|
for(t=0; t<this.list.length; t++) {
|
|
|
|
if(hasRefreshed || !this.children[t] || this.children[t].parseTreeNode.itemTitle !== this.list[t]) {
|
|
|
|
if(this.children[t]) {
|
|
|
|
this.removeListItem(t);
|
|
|
|
}
|
|
|
|
this.insertListItem(t,this.list[t]);
|
2023-09-24 19:19:50 +00:00
|
|
|
if(!hasRefreshed && t === oldLength) {
|
|
|
|
mustRefreshOldLast = true;
|
|
|
|
}
|
2021-04-20 08:15:11 +00:00
|
|
|
hasRefreshed = true;
|
|
|
|
} else {
|
|
|
|
// Refresh the item we're reusing
|
|
|
|
var refreshed = this.children[t].refresh(changedTiddlers);
|
|
|
|
hasRefreshed = hasRefreshed || refreshed;
|
|
|
|
}
|
|
|
|
}
|
2023-09-24 19:19:50 +00:00
|
|
|
// If items were inserted then we must recreate the item that used to be at the last position as it is no longer last
|
|
|
|
if(mustRefreshOldLast && oldLength > 0) {
|
|
|
|
var oldLastIdx = oldLength-1;
|
|
|
|
this.removeListItem(oldLastIdx);
|
|
|
|
this.insertListItem(oldLastIdx,this.list[oldLastIdx]);
|
|
|
|
}
|
2021-07-15 20:59:07 +00:00
|
|
|
// If there are items to remove and we have not refreshed then recreate the item that will now be at the last position
|
|
|
|
if(!hasRefreshed && this.children.length > this.list.length) {
|
|
|
|
this.removeListItem(this.list.length-1);
|
|
|
|
this.insertListItem(this.list.length-1,this.list[this.list.length-1]);
|
|
|
|
}
|
2021-04-20 08:15:11 +00:00
|
|
|
} else {
|
|
|
|
// Cycle through the list, inserting and removing list items as needed
|
2023-11-25 09:35:05 +00:00
|
|
|
var mustRecreateLastItem = false;
|
|
|
|
if(this.join && this.join.length) {
|
|
|
|
if(this.children.length !== this.list.length) {
|
|
|
|
mustRecreateLastItem = true;
|
|
|
|
} else if(prevList[prevList.length-1] !== this.list[this.list.length-1]) {
|
|
|
|
mustRecreateLastItem = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var isLast = false, wasLast = false;
|
2021-04-20 08:15:11 +00:00
|
|
|
for(t=0; t<this.list.length; t++) {
|
2023-11-25 09:35:05 +00:00
|
|
|
isLast = t === this.list.length-1;
|
2021-04-20 08:15:11 +00:00
|
|
|
var index = this.findListItem(t,this.list[t]);
|
2023-11-25 09:35:05 +00:00
|
|
|
wasLast = index === this.children.length-1;
|
|
|
|
if(wasLast && (index !== t || this.children.length !== this.list.length)) {
|
|
|
|
mustRecreateLastItem = !!(this.join && this.join.length);
|
|
|
|
}
|
2021-04-20 08:15:11 +00:00
|
|
|
if(index === undefined) {
|
|
|
|
// The list item must be inserted
|
2023-11-25 09:35:05 +00:00
|
|
|
if(isLast && mustRecreateLastItem && t>0) {
|
|
|
|
// First re-create previosly-last item that will no longer be last
|
|
|
|
this.removeListItem(t-1);
|
|
|
|
this.insertListItem(t-1,this.list[t-1]);
|
|
|
|
}
|
2021-04-20 08:15:11 +00:00
|
|
|
this.insertListItem(t,this.list[t]);
|
2013-10-12 16:05:13 +00:00
|
|
|
hasRefreshed = true;
|
2021-04-20 08:15:11 +00:00
|
|
|
} else {
|
|
|
|
// There are intervening list items that must be removed
|
|
|
|
for(var n=index-1; n>=t; n--) {
|
|
|
|
this.removeListItem(n);
|
|
|
|
hasRefreshed = true;
|
|
|
|
}
|
2023-11-25 09:35:05 +00:00
|
|
|
// Refresh the item we're reusing, or recreate if necessary
|
|
|
|
if(mustRecreateLastItem && (isLast || wasLast)) {
|
|
|
|
this.removeListItem(t);
|
|
|
|
this.insertListItem(t,this.list[t]);
|
|
|
|
hasRefreshed = true;
|
|
|
|
} else {
|
|
|
|
var refreshed = this.children[t].refresh(changedTiddlers);
|
|
|
|
hasRefreshed = hasRefreshed || refreshed;
|
|
|
|
}
|
2013-10-12 16:05:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Remove any left over items
|
|
|
|
for(t=this.children.length-1; t>=this.list.length; t--) {
|
|
|
|
this.removeListItem(t);
|
|
|
|
hasRefreshed = true;
|
|
|
|
}
|
|
|
|
return hasRefreshed;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Find the list item with a given title, starting from a specified position
|
|
|
|
*/
|
|
|
|
ListWidget.prototype.findListItem = function(startIndex,title) {
|
|
|
|
while(startIndex < this.children.length) {
|
|
|
|
if(this.children[startIndex].parseTreeNode.itemTitle === title) {
|
|
|
|
return startIndex;
|
|
|
|
}
|
|
|
|
startIndex++;
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Insert a new list item at the specified index
|
|
|
|
*/
|
|
|
|
ListWidget.prototype.insertListItem = function(index,title) {
|
2013-11-04 22:22:28 +00:00
|
|
|
// Create, insert and render the new child widgets
|
2021-04-20 08:15:11 +00:00
|
|
|
var widget = this.makeChildWidget(this.makeItemTemplate(title,index));
|
2013-11-04 22:22:28 +00:00
|
|
|
widget.parentDomNode = this.parentDomNode; // Hack to enable findNextSiblingDomNode() to work
|
|
|
|
this.children.splice(index,0,widget);
|
|
|
|
var nextSibling = widget.findNextSiblingDomNode();
|
|
|
|
widget.render(this.parentDomNode,nextSibling);
|
|
|
|
// Animate the insertion if required
|
|
|
|
if(this.storyview && this.storyview.insert) {
|
|
|
|
this.storyview.insert(widget);
|
|
|
|
}
|
2013-10-12 16:05:13 +00:00
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
2013-11-04 22:22:28 +00:00
|
|
|
Remove the specified list item
|
2013-10-12 16:05:13 +00:00
|
|
|
*/
|
|
|
|
ListWidget.prototype.removeListItem = function(index) {
|
2013-11-04 22:22:28 +00:00
|
|
|
var widget = this.children[index];
|
|
|
|
// Animate the removal if required
|
|
|
|
if(this.storyview && this.storyview.remove) {
|
|
|
|
this.storyview.remove(widget);
|
|
|
|
} else {
|
2023-05-18 16:27:05 +00:00
|
|
|
widget.removeChildDomNodes();
|
2013-11-04 22:22:28 +00:00
|
|
|
}
|
2013-10-12 16:05:13 +00:00
|
|
|
// Remove the child widget
|
|
|
|
this.children.splice(index,1);
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.list = ListWidget;
|
|
|
|
|
|
|
|
var ListItemWidget = function(parseTreeNode,options) {
|
|
|
|
this.initialise(parseTreeNode,options);
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Inherit from the base widget class
|
|
|
|
*/
|
|
|
|
ListItemWidget.prototype = new Widget();
|
|
|
|
|
|
|
|
/*
|
|
|
|
Render this widget into the DOM
|
|
|
|
*/
|
|
|
|
ListItemWidget.prototype.render = function(parent,nextSibling) {
|
|
|
|
this.parentDomNode = parent;
|
|
|
|
this.computeAttributes();
|
|
|
|
this.execute();
|
|
|
|
this.renderChildren(parent,nextSibling);
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Compute the internal state of the widget
|
|
|
|
*/
|
|
|
|
ListItemWidget.prototype.execute = function() {
|
|
|
|
// Set the current list item title
|
2015-02-13 22:42:36 +00:00
|
|
|
this.setVariable(this.parseTreeNode.variableName,this.parseTreeNode.itemTitle);
|
2021-04-26 13:41:26 +00:00
|
|
|
if(this.parseTreeNode.counterName) {
|
|
|
|
this.setVariable(this.parseTreeNode.counterName,this.parseTreeNode.counter);
|
|
|
|
this.setVariable(this.parseTreeNode.counterName + "-first",this.parseTreeNode.isFirst ? "yes" : "no");
|
|
|
|
this.setVariable(this.parseTreeNode.counterName + "-last",this.parseTreeNode.isLast ? "yes" : "no");
|
2021-04-20 08:15:11 +00:00
|
|
|
}
|
2023-11-25 09:35:05 +00:00
|
|
|
// Add join if needed
|
|
|
|
var children = this.parseTreeNode.children,
|
|
|
|
join = this.parseTreeNode.join;
|
|
|
|
if(join && join.length && !this.parseTreeNode.isLast) {
|
|
|
|
children = children.slice(0);
|
|
|
|
$tw.utils.each(join,function(joinNode) {
|
|
|
|
children.push(joinNode);
|
|
|
|
})
|
|
|
|
}
|
2013-10-12 16:05:13 +00:00
|
|
|
// Construct the child widgets
|
2023-11-25 09:35:05 +00:00
|
|
|
this.makeChildWidgets(children);
|
2013-10-12 16:05:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
|
|
|
|
*/
|
|
|
|
ListItemWidget.prototype.refresh = function(changedTiddlers) {
|
|
|
|
return this.refreshChildren(changedTiddlers);
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.listitem = ListItemWidget;
|
|
|
|
|
Alternate fix for inconsistent list template syntax (#7827)
* Alternate fix for inconsistent list template syntax
First attempt, which fails on the ListWidget/WithMissingTemplate test.
* Make WithMissingTemplate test pass, inefficiently
Unfortunately, this ends up being very inefficient, because the
clone-and-mutate logic is repeated for every list item. Not ideal.
* More efficient way to do it
This also makes the failing test pass, but far more efficiently.
* Improve performance of list template discovery
Since parse tree nodes never change after widget creation (whereas
attribute values *can* change), we can safely search for the explicit
list templtaes only once, at widget creation time. This saves time as
the search doesn't have to be done on each re-render, and also allows us
to safely do a clone-and-mutate step to extract the list widget's body
(if any) without any `$list-empty` or other items. That, in turn, allows
using the list widget's body as the template even if `$list-empty` is
specified inside the widget body.
2023-11-06 21:18:31 +00:00
|
|
|
/*
|
|
|
|
Make <$list-template> and <$list-empty> widgets that do nothing
|
|
|
|
*/
|
|
|
|
var ListTemplateWidget = function(parseTreeNode,options) {
|
|
|
|
// Main initialisation inherited from widget.js
|
|
|
|
this.initialise(parseTreeNode,options);
|
|
|
|
};
|
|
|
|
ListTemplateWidget.prototype = new Widget();
|
|
|
|
ListTemplateWidget.prototype.render = function() {}
|
|
|
|
ListTemplateWidget.prototype.refresh = function() { return false; }
|
|
|
|
|
|
|
|
exports["list-template"] = ListTemplateWidget;
|
|
|
|
|
|
|
|
var ListEmptyWidget = function(parseTreeNode,options) {
|
|
|
|
// Main initialisation inherited from widget.js
|
|
|
|
this.initialise(parseTreeNode,options);
|
|
|
|
};
|
|
|
|
ListEmptyWidget.prototype = new Widget();
|
|
|
|
ListEmptyWidget.prototype.render = function() {}
|
|
|
|
ListEmptyWidget.prototype.refresh = function() { return false; }
|
|
|
|
|
|
|
|
exports["list-empty"] = ListEmptyWidget;
|
|
|
|
|
2023-11-25 09:35:05 +00:00
|
|
|
var ListJoinWidget = function(parseTreeNode,options) {
|
|
|
|
// Main initialisation inherited from widget.js
|
|
|
|
this.initialise(parseTreeNode,options);
|
|
|
|
};
|
|
|
|
ListJoinWidget.prototype = new Widget();
|
|
|
|
ListJoinWidget.prototype.render = function() {}
|
|
|
|
ListJoinWidget.prototype.refresh = function() { return false; }
|
|
|
|
|
|
|
|
exports["list-join"] = ListJoinWidget;
|
|
|
|
|
2021-01-09 13:25:48 +00:00
|
|
|
})();
|