Welcome to TiddlyWiki created by Jeremy Ruston; Copyright © 2004-2007 Jeremy Ruston, Copyright © 2007-2011 UnaMesa Association
<<courseAdminPanel>>/%
|Автор |Александр Евгеньевич Доброчаев|
|Аудитория |5-6 класс|
|Картинка в шапке |mrd/header.jpg|
|courseId |mrd|
!!!Описание
<p>Первый курс из цикла "Ботаника" посвящен морфологии растений. Морфология — это наука, изучающая внешнее строение чего-либо. Именно с описания внешнего строения растений и их органов, мы и начнем изучение растительных организмов.</p>
<p><img src="mrd/author.jpg" class="pull-right" width="200px" /><b>Александр Евгеньевич Доброчаев</b></p>
<p>Кандидат биологических наук, преподаватель школы "Интеллектуал" с 2003 года, заведующий кафедрой биологии.</p>
!!!In title
Курс находится в разработке
!%/
|visibleName|Разнообразие побегов: урок 2|
<<lessonAdminPanel>>/%
!!!Описание
Продолжаем знакомство с древесными формами побегов. Теперь рассмотрим побеги клена и рябины.
!%/
|visibleName|Разнообразие побегов: урок 3|
<<lessonAdminPanel>>/%
!!!Описание
На последнем занятии по разнообразию древесных побегов мы рассмотрим побеги сосны и лиственницы.
!%/
<<lessonAdminPanel>>/%
!!!Описание
Во втором уроке курса "Морфология растений" мы узнаем о том, как растения изменяются в течении своей жизни и как они выглядят в самые первые дни своей жизни.
!%/
<<lessonAdminPanel>>/%
!!!Описание
Части организма, которые легко отграничить от других частей, называются органами. У растений органов немного, зато они многократно повторяются. Давайте подробнее изучим основные органы растений.
!%/
<<lessonAdminPanel>>/%
!!!Описание
Теперь, когда мы познакомились со строением побегов, мы можем ознакомиться с тем, как выглядят побеги у разных растений. Начнем с древесных растений — яблони, вишни и ели.
!%/
<<lessonAdminPanel>>/%
!!!Описание
Теперь, когда мы узнали из каких частей состоят растения, мы познакомимся со строением их основной части — побега.
!%/
|visibleName|Клен|
|тип шага|video|
|ссылка на контент шага|https://www.youtube.com/watch?v=lifQuNQnfM4|
|visibleName|Простые и сложные листья|
|тип шага|task|
|ссылка на контент шага|./mrd/dummy.html|
|visibleName|Листовые и побеговые рубцы|
|тип шага|task|
|ссылка на контент шага|./mrd/dummy.html|
|visibleName|Рябина|
|тип шага|video|
|ссылка на контент шага|https://www.youtube.com/watch?v=CBaMVLxfk6E|
|visibleName|Побег рябины|
|тип шага|task|
|ссылка на контент шага|./mrd/dummy.html|
|visibleName|Сложные листья|
|тип шага|pager|
|ссылка на контент шага|./mrd/png/mrd_05_01.png|
|visibleName|Длина междоузлий|
|тип шага|pager|
|ссылка на контент шага|./mrd/png/mrd_05_02.png|
|visibleName|Рубцы от листьев и от побегов|
|тип шага|pager|
|ссылка на контент шага|./mrd/png/mrd_05_03.png|
|visibleName|Разнообразие побегов: урок 2|
|тип шага|test|
|ссылка на контент шага|./mrd/task_05-4/index.html|
|ссылка на материал|./mrd/pdf/mrd_05.pdf|
|visibleName|Конспект лекции|
|занимает памяти|8,1 Мб|
|тип шага|video|
|visibleName|Сосна|
|ссылка на контент шага|https://www.youtube.com/watch?v=8dewzjnfWYk|
|тип шага|pager|
|visibleName|Побег сосны|
|ссылка на контент шага|./mrd/png/mrd_06_01.png|
|тип шага|pager|
|visibleName|Побег сосны|
|ссылка на контент шага|./mrd/png/mrd_06_02.png|
|тип шага|pager|
|visibleName|Листья сосны|
|ссылка на контент шага|./mrd/png/mrd_06_03.png|
|тип шага|task|
|visibleName|Побег сосны|
|ссылка на контент шага|./mrd/dummy.html|
|тип шага|video|
|visibleName|Лиственница|
|ссылка на контент шага|https://www.youtube.com/watch?v=ih18zlW7qcU|
|тип шага|pager|
|visibleName|Лиственница|
|ссылка на контент шага|./mrd/png/mrd_06_04.png|
|тип шага|task|
|visibleName|Побег лиственницы|
|ссылка на контент шага|./mrd/dummy.html|
|тип шага|task|
|visibleName|Листья на разных деревьях|
|ссылка на контент шага|./mrd/dummy.html|
|тип шага|test|
|ссылка на контент шага|./mrd/task_06-4/index.html|
|visibleName|Разнообразие побегов: урок 3|
|ссылка на материал|./mrd/pdf/mrd_06.pdf|
|visibleName|Конспект лекции|
|занимает памяти|9,9 Мб|
|ссылка на контент шага|mrd/mrd2-2.png|
|тип шага|pager|
|ссылка на контент шага|mrd/mrd1_5.html|
|тип шага|task|
|contentHeight|500|
|ссылка на контент шага|https://www.youtube.com/watch?v=tupZeOnyGqc&list=PLqBfxn8OBMGo-EOZDXqU8R_3sY6odb7d0&index=11|
|тип шага|video|
|ссылка на контент шага|mrd/mrd1_3.html|
|тип шага|task|
|contentHeight|500|
|ссылка на контент шага|mrd/mrd2-3.png|
|тип шага|pager|
|ссылка на контент шага|mrd/mrd1_4.html|
|тип шага|task|
|contentHeight|500|
|ссылка на контент шага|mrd/mrd2-4.png|
|тип шага|pager|
|ссылка на контент шага|mrd/mrd_tests/mrd_tests_1.2.html|
|тип шага|test|
|contentHeight|1200|
|ссылка на материал|mrd/mrd2.pdf|
|занимает памяти|5.5 MB|
|ссылка на контент шага|https://www.youtube.com/watch?v=IT1s9TSh-BY&list=PLqBfxn8OBMGo-EOZDXqU8R_3sY6odb7d0&index=12|
|тип шага|video|
|ссылка на контент шага|mrd/mrd2-1.png|
|тип шага|pager|
|ссылка на контент шага|https://www.youtube.com/watch?v=eFhJikqFtYA&list=PLqBfxn8OBMGo-EOZDXqU8R_3sY6odb7d0&index=15|
|тип шага|video|
|ссылка на контент шага|mrd/mrd1_1.html|
|тип шага|task|
|contentHeight|500|
|ссылка на контент шага|mrd/mrd1-1.png|
|тип шага|pager|
|ссылка на материал|mrd/mrd1.pdf|
|занимает памяти|7.3 MB|
|ссылка на контент шага|mrd/mrd1_2.html|
|тип шага|task|
|contentHeight|500|
|ссылка на контент шага|https://www.youtube.com/watch?v=8kpG7Ps0L-0&list=PLqBfxn8OBMGo-EOZDXqU8R_3sY6odb7d0&index=13|
|тип шага|video|
|ссылка на контент шага|mrd/mrd1-4.png|
|тип шага|pager|
|ссылка на контент шага|mrd/mrd_tests/mrd_tests_1.1.html|
|тип шага|test|
|contentHeight|1300|
|ссылка на контент шага|https://www.youtube.com/watch?v=MBG-fP5PnK8&list=PLqBfxn8OBMGo-EOZDXqU8R_3sY6odb7d0&index=14|
|тип шага|video|
|ссылка на контент шага|mrd/mrd1-3.png|
|тип шага|pager|
|ссылка на контент шага|mrd/mrd1-2.png|
|тип шага|pager|
|visibleName|Конспект лекции|
|ссылка на материал|./mrd/pdf/mrd_04.pdf|
|занимает памяти|1,1 Мб|
|тип шага|pager|
|ссылка на контент шага|./mrd/png/mrd_04_04.png|
|тип шага|video|
|ссылка на контент шага|https://www.youtube.com/watch?v=xA_yxlRKixo|
|тип шага|pager|
|ссылка на контент шага|./mrd/png/mrd_04_05.png|
|тип шага|video|
|ссылка на контент шага|https://www.youtube.com/watch?v=CPgwZnrCqi0|
|тип шага|pager|
|ссылка на контент шага|./mrd/png/mrd_04_03.png|
|тип шага|task|
|ссылка на контент шага|./mrd/dummy.html|
|тип шага|task|
|ссылка на контент шага|./mrd/dummy.html|
|тип шага|pager|
|ссылка на контент шага|./mrd/png/mrd_04_01.png|
|тип шага|pager|
|ссылка на контент шага|./mrd/png/mrd_04_02.png|
|тип шага|task|
|ссылка на контент шага|./mrd/dummy.html|
|visibleName|Разнообразие побегов: урок 1|
|тип шага|test|
|ссылка на контент шага|./mrd/task_04-4/index.html|
|тип шага|video|
|ссылка на контент шага|https://www.youtube.com/watch?v=KJOU0jhZagk|
|ссылка на контент шага|https://www.youtube.com/watch?v=enIH8NZw5jM&list=PLqBfxn8OBMGo-EOZDXqU8R_3sY6odb7d0&index=8|
|тип шага|video|
|ссылка на контент шага|mrd/mrd2_2.html|
|тип шага|task|
|contentHeight|500|
|ссылка на контент шага|mrd/mrd2_3.html|
|тип шага|task|
|contentHeight|500|
|ссылка на контент шага|mrd/mrd3-5.png|
|тип шага|pager|
|ссылка на контент шага|mrd/mrd3-4.png|
|тип шага|pager|
|ссылка на материал|mrd/mrd3.pdf|
|занимает памяти|6 MB|
|ссылка на контент шага|https://www.youtube.com/watch?v=NtQvdTcfvoA&list=PLqBfxn8OBMGo-EOZDXqU8R_3sY6odb7d0&index=9|
|тип шага|video|
|ссылка на контент шага|mrd/mrd3-3.png|
|тип шага|pager|
|ссылка на контент шага|https://www.youtube.com/watch?v=zfWGNeODBag&list=PLqBfxn8OBMGo-EOZDXqU8R_3sY6odb7d0&index=10|
|тип шага|video|
|ссылка на контент шага|mrd/mrd2_1.html|
|тип шага|task|
|contentHeight|500|
|ссылка на контент шага|mrd/mrd3-2.png|
|тип шага|pager|
|ссылка на контент шага|mrd/mrd3-1.png|
|тип шага|pager|
|ссылка на контент шага|mrd/mrd_tests/mrd_tests_1.3.html|
|тип шага|test|
|contentHeight|1700|
/***
|Version|1.0|
|Short status|<<insertEditable container:"@stateComment" size:max cell>>|
tests: [[BRP 1]], [[BRP 2]], [[BRP 3]]
***/
//{{{
var orig_BRP_changed = Tiddler.prototype.changed;
// this recalcs links according to config.textPrimitives.tiddlerAnyLinkRegExp and
// config.textPrimitives.tiddlerForcedLinkRegExp , so temporarily hijack them
Tiddler.prototype.changed = function()
{
var brackettedLinkRE = config.textPrimitives.brackettedLink,
// titledBrackettedLinkRE = config.textPrimitives.titledBrackettedLink,
sectionOrSliceAddition =
"(?:(?:(?:"+config.textPrimitives.sliceSeparator+"[^\\|\\n\\]]+)|"+ //::
"(?:"+config.textPrimitives.sectionSeparator+"[^\\n\\]]+))?)", //##
tiddlerForcedLinkRegExp = config.textPrimitives.tiddlerForcedLinkRegExp,
tiddlerAnyLinkRegExp = config.textPrimitives.tiddlerAnyLinkRegExp;
// hijack REs
config.textPrimitives.brackettedLink = "\\[\\[([^\\]]+?)"+ // extra "?" is important here
sectionOrSliceAddition+
"\\]\\]";
// core definition: "\\[\\[([^\\]]+)\\]\\]";
// config.textPrimitives.titledBrackettedLink = "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+?)"+
// sectionOrSliceAddition+
// "\\]\\]";
// core definition: "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]";
// recalc, as in the core:
config.textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" +
config.textPrimitives.titledBrackettedLink + ")|(?:" +
config.textPrimitives.brackettedLink + ")|(?:" +
config.textPrimitives.urlPattern + ")","mg");
config.textPrimitives.tiddlerAnyLinkRegExp = new RegExp("("+
config.textPrimitives.wikiLink + ")|(?:" +
config.textPrimitives.titledBrackettedLink + ")|(?:" +
config.textPrimitives.brackettedLink + ")|(?:" +
config.textPrimitives.urlPattern + ")","mg");
var result = orig_BRP_changed.apply(this,arguments);
// unhijack REs
config.textPrimitives.brackettedLink = brackettedLinkRE;
// config.textPrimitives.titledBrackettedLink = titledBrackettedLinkRE;
// recalc again
config.textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" +
config.textPrimitives.titledBrackettedLink + ")|(?:" +
config.textPrimitives.brackettedLink + ")|(?:" +
config.textPrimitives.urlPattern + ")","mg");
config.textPrimitives.tiddlerAnyLinkRegExp = new RegExp("("+
config.textPrimitives.wikiLink + ")|(?:" +
config.textPrimitives.titledBrackettedLink + ")|(?:" +
config.textPrimitives.brackettedLink + ")|(?:" +
config.textPrimitives.urlPattern + ")","mg");
return result; // in fact, there's no result, this is for possible future extensions
};
//}}}
/***
|Description|This plugin fixes a couple of macro params issues:<br>1) fixes macro error in some cases when a named param has {{{{{}}}-containing value;<br>2) makes "escaped" ' and " in macro params be really escaped (examples: {{{"some \"value\""}}} is parsed as {{{some "value"}}} and {{{'that\'s nice'}}} becomes {{{that's nice}}})|
|Version|1.0|
|Short status|<<insertEditable container:"@stateComment" size:max cell>>|
|Author|Yakov Litvin|
***/
//{{{
// fix the error rising for macros with '{{'-containing named param's value
String.prototype.readMacroParams = function(notAllowEval)
{
// YL tweak: don't use "noNames"
var p = this.parseParams("_default",null,!notAllowEval);
var t, n = [];
for(t = 1; t < p.length; t++)
n.push(p[t].value);
return n;
};
// implement escaping of ' and " in macro params
String.prototype.parseParams = function(defaultName,defaultValue,allowEval,noNames,cascadeDefaults)
{
var parseToken = function(match,p) {
var n;
if(match[p]) // Double quoted
// YL tweak: substitute escaped \" with "
n = match[p].replace(/\\"/g,'"');
else if(match[p+1]) // Single quoted
// YL tweak: substitute escaped \' with '
n = match[p+1].replace(/\\'/g,"'");
else if(match[p+2]) // Double-square-bracket quoted
n = match[p+2];
else if(match[p+3]) // Double-brace quoted
try {
n = match[p+3];
if(allowEval && config.evaluateMacroParameters != "none") {
if(config.evaluateMacroParameters == "restricted") {
if(window.restrictedEval)
n = window.restrictedEval(n);
} else
n = window.eval(n);
}
} catch(ex) {
throw "Unable to evaluate {{" + match[p+3] + "}}: " + exceptionText(ex);
}
else if(match[p+4]) // Unquoted
n = match[p+4];
else if(match[p+5]) // empty quote
n = "";
return n;
};
var r = [{}];
var dblQuote = "(?:\"((?:(?:\\\\\")|[^\"])+)\")";
var sngQuote = "(?:'((?:(?:\\\\\')|[^'])+)')";
var dblSquare = "(?:\\[\\[((?:\\s|\\S)*?)\\]\\])";
var dblBrace = "(?:\\{\\{((?:\\s|\\S)*?)\\}\\})";
var unQuoted = noNames ? "([^\"'\\s]\\S*)" : "([^\"':\\s][^\\s:]*)";
var emptyQuote = "((?:\"\")|(?:''))";
var skipSpace = "(?:\\s*)";
var token = "(?:" + dblQuote + "|" + sngQuote + "|" + dblSquare + "|" + dblBrace + "|" + unQuoted + "|" + emptyQuote + ")";
var re = noNames ? new RegExp(token,"mg") : new RegExp(skipSpace + token + skipSpace + "(?:(\\:)" + skipSpace + token + ")?","mg");
var match;
do {
match = re.exec(this);
if(match) {
var n = parseToken(match,1);
if(noNames) {
r.push({ name:"", value:n });
} else {
var v = parseToken(match,8);
if(v == null && defaultName) {
v = n;
n = defaultName;
} else if(v == null && defaultValue) {
v = defaultValue;
}
r.push({ name:n, value:v });
if(cascadeDefaults) {
defaultName = n;
defaultValue = v;
}
}
}
} while(match);
// Summarise parameters into first element
var t;
for(t = 1; t < r.length; t++)
if(r[0][r[t].name])
r[0][r[t].name].push(r[t].value);
else
r[0][r[t].name] = [r[t].value];
return r;
};
//}}}
//{{{
// --- theme switching -------------------------------------
var usedOnline = window.location.protocol != "file:";
var usedWithBackEnd = !!window.saveOnlineChanges;
var visitorThemeName = "WebTheme";
var authorThemeName = "AuthorTheme";
readOnly = usedOnline && !usedWithBackEnd;
// theme setting helpers
story.setVisitorTheme = function() {
story.switchTheme(visitorThemeName);
//# test (online auto-setting, including web config.options.txtTheme is saved)
};
story.setAuthorTheme = function() {
story.switchTheme(authorThemeName);
//# test (online auto-setting, including web config.options.txtTheme is saved)
};
story.toggleRoleTheme = function() {
if(config.options.txtTheme == visitorThemeName)
story.setAuthorTheme();
else
story.setVisitorTheme();
};
// set theme on startup
if(usedOnline && !usedWithBackEnd) {
var ensureTheme = function(){
if(config.options.txtTheme != visitorThemeName)
story.setVisitorTheme();
}
setInterval(ensureTheme,100);
} else {
var ensureTheme = function(){
if(config.options.txtTheme != authorThemeName)
story.setAuthorTheme();
}
story.setAuthorTheme();
//# dirty hacky way:
setTimeout(ensureTheme,100);
setTimeout(ensureTheme,200);
setTimeout(ensureTheme,300);
setTimeout(ensureTheme,400);
setTimeout(ensureTheme,500);
setTimeout(ensureTheme,600);
}
// enable theme switching by pressing ctrl+shift+t
jQuery(document).on('keydown',function(e){
if(usedOnline && !usedWithBackEnd) return;
if((e.which == 222 /*'*/ || e.which == 0) && e.ctrlKey && e.shiftKey) {
story.toggleRoleTheme();
if(e.preventDefault)
e.preventDefault();
return false;
}
});
// display different tiddlers for an author and a visitor:
story.orig_displayDefaultTiddlers = story.displayDefaultTiddlers;
story.displayDefaultTiddlers = function()
{
if(usedOnline && !usedWithBackEnd)
this.displayTiddlers(null, store.filterTiddlers(store.getTiddlerText("WebDefaultTiddlers")||""));
else
this.orig_displayDefaultTiddlers();
};
// disable including for visitors (prevent error message to appear)
config.options.chkUseInclude = !usedOnline;// || usedWithBackEnd;
// --- helpers ---------------------------------------------
function updateQueryParameter(uri, key, value) {
var re = new RegExp("([?&])" + key + "=.*?(&|#|$)", "i");
if (uri.match(re))
return uri.replace(re, '$1' + key + "=" + value + '$2');
else {
var hash = '';
if( uri.indexOf('#') !== -1 ){
hash = uri.replace(/.*#/, '#');
uri = uri.replace(/#.*/, '');
}
var separator = uri.indexOf('?') !== -1 ? "&" : "?";
return uri + separator + key + "=" + value + hash;
}
};
Tiddler.prototype.getVisibleName = function(sliceName,anotherDefault)
{
return this.getSlice(sliceName || "visibleName") || anotherDefault || this.title;
};
//
if(config.options.txtUnitStep === undefined)
config.options.txtUnitStep = 0;
if(config.options.txtLessonNumber === undefined)
config.options.txtLessonNumber = 0;
// --- unit step macros ------------------------------------
config.macros.unitStep =
{
getUnitSteps: function(tiddler) {
var tag = tiddler.title,
steps = store.filterTiddlers("set: tag["+tag+"] AND tag[unitStep]");
return config.macros.itemMenu.sortByCounter(steps/*, fieldName*/);
},
handler: function(place,macroName,params,wikifier,paramString,tiddler)
{
// lesson steps and current step
var steps = this.getUnitSteps(tiddler);
// header (navigation)
var header = createTiddlyElement(place,"div","","lessonNavigation","",{role:'navigation'});
var hrefToMain = "";
var linkToMain = hrefToMain ? createExternalLink(header,hrefToMain) :
jQuery('<a class="noLink"></a>').appendTo(header)[0];
createTiddlyElement(linkToMain,"div","","logo-sign");
// link to course contents (if not available, doesn't do anything)
var i, tag, theCourse;
for(i = 0; i < tiddler.tags.length; i++) {
tag = store.fetchTiddler(tiddler.tags[i]);
if(tag && tag.tags.contains("course"))
theCourse = tag;
}
var title = tiddler.getVisibleName();
if(theCourse) {
var linkToContents = createTiddlyLink(header,theCourse.title);
jQuery(linkToContents).html(title).addClass("linkToContents");
} else
jQuery("<a class='noLinkToContents'>"+title+"</a>")
.appendTo(header);
var navLink, type;
if(steps.length > 1)
for(let j = 0; j < steps.length; j++) // not compatible with elder browsers
{
navLink = createTiddlyElement(header,"a","","stepLink");
jQuery(navLink).on('click',function(){
config.options.txtUnitStep = j;
refreshStep();
});
if(type = steps[j].getSlice("тип шага"))
createTiddlyElement(navLink,"div","",type);
else
createTiddlyText(navLink,"шаг "+j);
}
// wrapper, 3 colomns and buttons (backward/forward)
var wrapper = createTiddlyElement(place,"div","","unitStep"),
leftColomn = createTiddlyElement(wrapper,"div","","goPrevContainer"),
contentColomn = createTiddlyElement(wrapper,"div","", "unitStepContentContainer"),
rightColomn = createTiddlyElement(wrapper,"div","","goNextContainer");
if(steps.length == 0)
return;
//# display something indicating that the lesson is empty for now
var backContainer = createTiddlyElement(leftColomn,"div","","backContainer");
var backButton = createTiddlyElement(backContainer,"div","","backward");
jQuery(leftColomn).on('click',function(){
config.options.txtUnitStep--;
refreshStep();
});
var forthContainer = createTiddlyElement(rightColomn,"div","","forthContainer");
var forwardButton = createTiddlyElement(forthContainer,"div","","forward");
jQuery(rightColomn).on('click',function(){
config.options.txtUnitStep++;
refreshStep();
});
var refreshStep = function()
{
// refresh content
jQuery(contentColomn).empty();
var step = steps[config.options.txtUnitStep];
var contentUrl = step ? step.getSlice("ссылка на контент шага") : "";
var specifiedHeight = step.getSlice("contentHeight");
if(specifiedHeight) specifiedHeight = parseInt(specifiedHeight);
if(contentUrl) {
var isImage = /\.(png|jpg|jpeg|gif)(\?.*)?$/i.exec(contentUrl);
var isVideo = /(?:youtube\.com)|(?:youtu\.be)|(?:^[-\w]+$)/i.exec(contentUrl)
|| step.getSlice("тип шага") == "video";
if(isVideo) {
/* deal with the following urls:
*
* https://www.youtube.com/watch?v=34c2GNgBpfE
* https://www.youtube.com/embed/34c2GNgBpfE <- canonical one, turn them into this form
* https://youtu.be/34c2GNgBpfE
* 34c2GNgBpfE
*/
var youtubeIdRE = /((?:youtube\.com\/watch\?v=)|(?:youtube\.com\/embed\/)|(?:youtu\.be\/))?([-\w]+)((\?.*)|(\&.*)|$)/,
youtubeIdMatch = youtubeIdRE.exec(contentUrl),
youtubeId = youtubeIdMatch ? youtubeIdMatch[2]:"";
if(youtubeId)
contentUrl = "https://www.youtube.com/embed/"+ youtubeId;
// disable autoplay, remove "related" from YouTube videos
contentUrl = updateQueryParameter(contentUrl, "autoplay", 0);
contentUrl = updateQueryParameter(contentUrl,"rel",0);
}
if(isImage)
jQuery("<img src='"+contentUrl+"' />")
.addClass("contentImage")
.appendTo(contentColomn);
else {
var iframe = createTiddlyElement(contentColomn,"iframe","", "contentIframe","",{
src: contentUrl,
frameborder: 0,
scrolling: "no",
was_reloaded: false
});
if(/\.html$/.exec(contentUrl))
iframe.onload = function(){
this.height = specifiedHeight || (this.contentWindow.document.body.offsetHeight +50);
if(!this.was_reloaded) //optional?
{
this.was_reloaded = true;
try {
this.contentDocument.location.reload();
}catch(ex){}
}
}
// +50 is a dirty hack; no idea why needed
else
// for video mostly
iframe.height = iframe.offsetWidth * 9/16;
}
}
//contentColomn.heigh = contentColomn.firstChild.offsetHeight; //# set via CSS instead
//# implement ?? for no-contentUrl case
// refresh navigation arrows
if(config.options.txtUnitStep == 0)
jQuery(backContainer).hide();
else
jQuery(backContainer).show();
if(config.options.txtUnitStep == steps.length-1)
jQuery(forthContainer).hide();
else
jQuery(forthContainer).show();
//# highlight the link to the opened step in the nav bar
jQuery(".currentStep").removeClass("currentStep");
jQuery(".stepLink:eq("+config.options.txtUnitStep+")") .addClass("currentStep");
}
refreshStep();
},
};
config.macros.lesson =
{
getAttachments: function(tiddler) {
var tag = tiddler.title,
atts = store.filterTiddlers("set: tag["+tag+"] AND tag[attachment]");
return config.macros.itemMenu.sortByCounter(atts/*, fieldName*/);
}
};
config.macros.unitStepFooter =
{
handler: function(place,macroName,params,wikifier,paramString,tiddler) {
//# implement
}
};
config.macros.lessonAdminPanel =
{
handler: function(place,macroName,params,wikifier,paramString,tiddler)
{
// parametric transclusion is useful for more fluid development
wikify('<<tiddler [[LessonAdminPanel]] with:[['+tiddler.title+']] noedit>>',place,null,tiddler);
}
};
// --- course macros ------------------------------------
config.macros.fillCourseHeader = {
handler: function(place,macroName,params,wikifier,paramString,tiddler)
{
var imgUrl = tiddler.getSlice("Картинка в шапке","");
if(imgUrl)
jQuery('#course-header').css("background",
'linear-gradient(to right,rgba(0,0,0,0.4), rgba(0,0,0,0) 80%) no-repeat left, url("'+imgUrl+'") no-repeat center');
jQuery("#main_content #course-header")
.empty()
.append(
'<h2 id="course-name">'+ tiddler.getVisibleName() +'</h2>'+
'<h4 id="course-author">'+ tiddler.getSlice("Автор","") +'</h4>'+
'<h4 id="course-audience">'+ tiddler.getSlice("Аудитория","")+'</h4>'+
(tiddler.getSection("In title",""))
);
}
};
config.macros.fillCourseContents = {
getCourseContents: function(course)
{
if(!course) return null;
//# may be add getting course tiddler if course is the title or courseId
var tids = store.filterTiddlers("[tag["+course.title+"]]"),
courseId = course.getSlice("courseId","");
return config.macros.itemMenu.sortByCounter(tids, courseId);
},
getCourseLessons: function(course)
{
var contents = this.getCourseContents(course), i;
if(!contents) return null;
for(i = 0; i < contents.length; i++)
if(!contents[i].tags.contains("unit"))
contents.splice(i--,1);
return contents;
},
handler: function(place,macroName,params,wikifier,paramString,tiddler)
{
var lessons = this.getCourseLessons(tiddler),
contents = this.getCourseContents(tiddler);
var getIndentIndex = function(itemTiddler) {
if(itemTiddler.tags.contains("unit")) return 0;
if(itemTiddler.tags.contains("заголовок 1 надуровня")) return 1;
if(itemTiddler.tags.contains("заголовок 2 надуровня")) return 2;
},
maxIndentIndex = 0;
for(n = 0; n < contents.length; n++)
if(getIndentIndex(contents[n]) > maxIndentIndex)
maxIndentIndex = getIndentIndex(contents[n]);
var $courseContentsColomn = jQuery("#lessons-list"),
$lessonContentsColomn = jQuery("#lesson-content");
$courseContentsColomn.empty();
// add the "about course" link (implement in refresher)
jQuery('<a class="lesson-name h4">О курсе</a>')
.on('click',function(){
config.options.txtLessonNumber = 0;
selectLesson();
}).appendTo($courseContentsColomn)
.wrap('<div class="lesson" />');
// add per lesson links
for(let i = 0; i < contents.length; i++) {
let item = contents[i], isLesson = item.tags.contains("unit");
let mute = isLesson && !store.getTaggedTiddlers(item.title).length && !item.getSection("Описание");
levelClass = isLesson ? 'h4' :
(item.tags.contains("заголовок 1 надуровня")? 'h3' : 'h2');
indentClass = "indent-"+(maxIndentIndex - getIndentIndex(item));
jQuery('<a class="lesson-name '+levelClass+' '+indentClass+(mute ? ' mute' : '')+'">'
+contents[i].getVisibleName()+'</a>')
.on('click',function(){
if(!isLesson || mute) return;
config.options.txtLessonNumber = lessons.indexOf(item)+1;
selectLesson();
}).appendTo($courseContentsColomn)
.wrap('<div class="lesson" />');
}
var selectLesson = function() {
// (re)set selection
jQuery('.lesson-active').removeClass("lesson-active");
var lessonNumber = config.options.txtLessonNumber-1,
lessonNumberAmongContents = lessonNumber == -1 ? 0 :
contents.indexOf(lessons[lessonNumber])+1;
jQuery(".lesson-name").eq(lessonNumberAmongContents).addClass("lesson-active");
$lessonContentsColomn.empty();
var $lessonContentsWrapper = jQuery('<div>').appendTo($lessonContentsColomn);
window.elementToKeepIntoView = $lessonContentsWrapper[0];
jQuery(window).scroll();
if(config.options.txtLessonNumber == 0) {
// show "about course"
let description = tiddler.getSection("Описание");
if(description)
$lessonContentsWrapper.append('<p>'+description+ '</p>');
/*if(lessons.length)
jQuery('<a class="btn btn-primary">Начать курс</a>')
.on('click',function(){
config.options.txtUnitStep = 0;
story.displayTiddler(this,lessons[0]);
})
.appendTo($lessonContentsWrapper)
*/
//# hr and teacher description
} else {
// show lesson
var lessonNumber = config.options.txtLessonNumber - 1;
if(lessonNumber >= lessons.length) lessonNumber = 0; // fixes a bug taking place when we jump from a course to another one that has fewer lessons
var lesson = lessons[lessonNumber],
title = lesson.title,
visibleTitle = lesson.getVisibleName(),
lessonDescription = lesson.getSection("Описание"),
goLabel = config.macros.unitStep.getUnitSteps(lesson).length > 1 ? "Пройти занятие" : "Посмотреть";
$lessonContentsWrapper
.append('<h2>'+visibleTitle+'</h2>')
//# number in h2/"Занятие #" as h5
.append('<p>'+lessonDescription+'</p>')
.append(config.macros.unitStep.getUnitSteps(lesson).length ?
jQuery('<a class="btn btn-primary">'+goLabel+'</a>')
.on('click',function(){
config.options.txtUnitStep = 0;
story.displayTiddler(this,title);
})
: '')
.append('<ol id="lesson-steps"/>')
var steps = config.macros.unitStep.getUnitSteps(lesson),
ruTypes = {
pager: "Конспект",
task: "Задание",
test: "Тест",
video: "Видео",
};
if(steps.length > 1)
for(let i = 0; i < steps.length; i++) {
let type = steps[i].getSlice("тип шага"),
ruType = ruTypes[type];
jQuery('<a>')
.appendTo("#lesson-steps")
.wrap('<li>').wrap('<p>')
.on('click',function(){
config.options.txtUnitStep = i;
story.displayTiddler(this,title);
})
//.append('<div class="pull-left '+type+'"/>')
.append(jQuery('<img/>',{
class: "pull-left",
src: "images/"+(type=="pager" ? "text" : type)+".svg", // типы, исп-ные до того, отл-ся от тех, что у Виктора
width: "24px",
height: "24px"
}))
.append('<span class="lesson-label">'+steps[i].getVisibleName()+'</span><br>'+
'<small>'+ruType+'</small>')
//# " - 5:99" (video length)
}
var attachments = config.macros.lesson.getAttachments( lesson ),
attName, attUrl, isPdf, attSize;
for(let i = 0; i < attachments.length; i++)
{
attName = attachments[i].getVisibleName();
attUrl = attachments[i].getSlice("ссылка на материал");
isPdf = /.pdf/i.exec(attUrl);
attSize = attachments[i].getSlice("занимает памяти");
jQuery('<a href="'+attUrl+'" target="_blank" />')
.appendTo($lessonContentsWrapper)
.wrap('<p>')
.append('<img src="images/dwnld_solid.svg" class="attachment-icon">'+
'<span class="attachment-label">'+attName+'</span>' +
(isPdf ? ' <img src="images/pdf.svg" class="pdf-attachment-icon">' : '') +
(!attSize ? '' : (isPdf ? ' <span class="file-size">'+attSize+'</span>' : ' (<span class="file-size">'+attSize+'</span>)'))
);
}
}
};
// pick the step (by default "about course")
selectLesson();
}
};
window.prevScroll = 0;
jQuery(window).scroll(function()
{
var menuEl = window.elementToKeepIntoView;
if(!menuEl) return;
var container = menuEl.parentElement;
if(!container) return;
var height = menuEl.offsetHeight,
windowHeight = findWindowHeight(),
scrollDown = window.prevScroll < findScrollY(),
scrollUp = window.prevScroll > findScrollY();
menuEl.style.position = "relative";
var topLimitByContainer = 0, topLimitByWindow = findScrollY() - findPosY(container) -50,
//# get rid of magic value: it's the container's padding-top in px
bottomLimitByContainer = container.offsetHeight -80, //jQuery(container).innerHeight() ?
//# get rid of magic value: it's the container's padding-top + padding-bottom in px
bottomLimitByWindow = topLimitByWindow + findWindowHeight() -20,
//# get rid of magic value: presumably, it's the header's margin-top in px
//re = /(\-?\d+(?:\.\d+)?)\s?px/i,
getPixels = function(value) {
if(!value) return 0;
//var match = re.exec(value);
//if(!match) return parseInt(value);
return parseInt(value);//match[1]); //test with non-px values (are they stored in .style.top?)
},
menuElTopCss = getPixels(menuEl.style.top);
if(!scrollDown && !scrollUp)
menuEl.style.top = topLimitByWindow + "px"; //attach top to window top
if(scrollDown) {
if(height < windowHeight) {
if(menuElTopCss < topLimitByWindow) // top edge is higher than that of window
menuEl.style.top = topLimitByWindow + "px"; //top to top
} else
if(menuElTopCss + height < bottomLimitByWindow) // bottom edge higher than that of window
menuEl.style.top = (bottomLimitByWindow - height) + "px"; //bottom to bottom
}
if(scrollUp) {
if(height < windowHeight)
menuEl.style.top = topLimitByWindow + "px"; //attach top to window top
else
if(menuElTopCss > topLimitByWindow) // top edge lower than that of window
menuEl.style.top = topLimitByWindow + "px"; //attach top to window top
}
//# deal with selectLesson 46 (ends up below the bottom of the container)
// somehow in the menuElTopCss + height > bottomLimitByContainer check below height == 0
// that's true for each selectLesson firing scroll: height == 0
// ensure we're inside the container
menuElTopCss = getPixels(menuEl.style.top);
if(menuElTopCss < topLimitByContainer)
menuEl.style.top = topLimitByContainer + "px";
console.log("height: "+height);
if(menuElTopCss + height > bottomLimitByContainer)
menuEl.style.top = (bottomLimitByContainer - height) + "px";
//# make scrolling smoother
window.prevScroll = findScrollY();
});
config.macros.courseAdminPanel = {
handler: function(place,macroName,params,wikifier,paramString,tiddler) {
if(!tiddler) return;
// parametric transclusion is useful for more fluid development
wikify('<<tiddler [[CourseAdminPanel]] with:[['+tiddler.title+']] noedit>>',place,null,tiddler);
}
};
// --- common macros ------------------------------------
// make common topLineMenu and footer? use config.macros.siteTopMenu?
//}}}
/***
|''Name''|DashesFormattersPlugin|
|''Version''|0.1|
|Short status|<<insertEditable container:"@stateComment" size:max cell>>|
test: a -- b, c --- d, --ef--
----
gh —
***/
//{{{
for(var i = 0; i < config.formatters.length; i++)
if(config.formatters[i].name == "mdash") {
config.formatters[i].name = "ndash";
config.formatters[i].handler = function(w) {
createTiddlyElement(w.output,"span").innerHTML = "–";
};
};
/*config.formatters.splice(i,0,{
name: "mdash",
match: "---",
handler: function(w)
{
createTiddlyElement(w.output,"span").innerHTML = "—";
}
});*/
//}}}
/***
|Version|1.4|
{{PoGc{дописать метаданные!}}} Пока у плагина нет документации и т.п., он базируется в репозитории напару с ~GridPlugin
* в версии 1.2 исправлен тот факт, что ":" включался в slice name
* в версии 1.3 изменено поведение в отношении начальных/конечных пробелов/табуляций вокруг slice name
* в версии 1.4 исправлено поведение, когда пробелы в конце "второго столбца" добавляются в slice value
* когда пробел обязательно заменять на \x20? можно ли тут обойтись без этого?
***/
//{{{
TiddlyWiki.prototype.slicesRE = /(?:^([\'\/]{0,2})~?([\.\w]+)\:\1[\t\x20]*([^\n]+)[\t\x20]*$)|(?:^\|\x20?([\'\/]{0,2})~?([^\|\s\:\~\'\/]|(?:[^\|\s~\'\/][^\|\n\f\r]*[^\|\s\:\'\/]))\:?\4[\x20\t]*\|[\t\x20]*([^\n\t\x20](?:[^\n]*[^\n\t\x20])?)[\t\x20]*\|$)/gm;
store.slices = {};
//}}}
/***
|''Version''|1.4|
|''Requires''|UpToDateFiltersPlugin|
|~|requires http://yakovl.bplaced.net/TW/ExtraFilters.html#UpToDateFiltersPlugin only if TW core version is 2.6.1 or below|
|''Source''|http://yakovl.bplaced.net/TW/ExtraFilters.html#ExtraFiltersPlugin|
|''Documentation''|http://yakovl.bplaced.net/TW/ExtraFilters.html#ExtraFiltersPluginInfo|
|''Author''|Yakov Litvin|
|''Licence''|[[BSD-like open source license|http://yakovl.bplaced.net/TW/ExtraFilters.html#%5B%5BYakov%20Litvin%20Public%20Licence%5D%5D]] |
***/
//{{{
if(!config.filters.all)
config.filters.all = function(results,match) { // v1.0
this.forEachTiddler(function(tName,tiddler){
results.pushUnique(tiddler);
});
return results;
};
config.filters.and = function(results,match) { // v1.0
// parse the argument as "filterName[filterParam"
var dividingRE = /([^\[\]]+)\[([^\]]*)/,
filterParts = dividingRE.exec(match[3]);
if(filterParts) {
var filterName = filterParts[1],
filterParam = filterParts[2];
} else
throw("\"and\" filter: wrong syntax");
// create the set of filtered tiddlers
var filter = "[" + filterName + "[" + filterParam + "]]",
tids = this.filterTiddlers(filter),
newResult = [];
// collect tiddlers present among both "results" and filtered tiddlers
for(var i = 0; i < results.length; i++)
for(var j = 0; j < tids.length; j++)
if(results[i] == tids[j])
newResult.push(tids[j]);
return newResult;
};
config.filters.not = function(results,match) { // v1.0
// parse the argument as "filterName[filterParam"
var dividingRE = /([^\[\]]*)\[([^\]]*)/,
filterParts = dividingRE.exec(match[3]);
if(filterParts) {
var filterName = filterParts[1],
filterParam = filterParts[2];
} else
throw("\"not\" filter: wrong syntax");
// create the set of filtered tiddlers
var filter = "[" + filterName + "[" + filterParam + "]]",
tids = this.filterTiddlers(filter);
// collect tiddlers present among "results", but not among filtered tiddlers
for(var i = 0; i < results.length; i++)
for(var j = 0; j < tids.length; j++)
if(results[i] == tids[j]) {
results.splice(i,1);
i--;
tids.splice(j,1);
break;
}
return results;
};
config.filters.tagTree = function(results, match)
{
results = results || [];
var depthRE = /^(\d),(.+)$/, depthMatch = depthRE.exec(match[3]),
depth = depthMatch ? parseInt(depthMatch[1]) : -1, currentDepth = 0,
root = depthMatch ? depthMatch[2] : match[3], tags = [root], prevLength,
i, tagTags, j;
var rootTid = store.fetchTiddler(root);
if(rootTid) results.pushUnique(rootTid);
// get tags, tags of tags etc ;
// for optimization, push to results inline
do {
prevLength = tags.length;
for(i = 0; i < prevLength; i++) //may be optimized by starting from i = ..
{
tagTags = store.getTaggedTiddlers(tags[i]);
for(j = 0; j < tagTags.length; j++) {
tags.pushUnique(tagTags[j].title);
// optimized place to push:
results.pushUnique(tagTags[j]);
}
}
currentDepth++;
} while (tags.length > prevLength && currentDepth != depth);
return results;
};
config.filters.unclassified = function(results,match) { // v1.0
var category = match[3],
instances = this.getTaggedTiddlers(category);
// filter out tiddlers tagged with instances
for(var i = 0; i < results.length; i++)
for(var j = 0; j < instances.length; j++)
if(results[i].isTagged(instances[j].title)) {
results.splice(i,1);
i--;
break;
}
return results;
};
config.filters.taggedOnly =
config.filters.oTag = function(results, match) { // v1.0
// parse param
var add = true, arg = match[3], i;
switch(arg.substr(0,1)) {
case "+":
// "add" is "true" already
arg = arg.substr(1);
break;
case "-":
add = false;
arg = arg.substr(1);
break;
}
var isTaggedOnly = function(tiddler,arg) {
return (tiddler.tags.length == 1) && (!arg || arg == tiddler.tags[0])
};
if(add) {
var tiddlers = this.reverseLookup();
for(i = 0; i < tiddlers.length; i++)
if(isTaggedOnly(tiddlers[i],arg))
results.pushUnique(tiddlers[i]);
} else {
for(i = 0; i < results.length; i++)
if(!isTaggedOnly(results[i],arg))
results.splice(i--,1);
}
return results;
};
config.filters.hasPart = function (results, match) { // v1.1
// parse the argument
var arg = match[3], reText, re, type, isBad, title;
switch(arg.substr(0,2)) {
case "##":
case "::":
isBad = function(title) {
return store.getTiddlerText(title + arg) ? false : true;
};
break
case "@@":
isBad = function(title) {
return store.getValue(title, arg.substr(2)) ? false : true;
};
break
case "r@": // regExp (for tiddler.text) mode
case "R@":
case "t@": // title mode
case "T@":
reText = store.getTiddlerText(arg.substr(2));
type = arg.substr(0,1);
if(!reText)
if(type == "r" || type == "t")
return results; // "forgiving mode", nothing is filtered out in this case
else
throw("RegExp for filtering is not found in " + arg.substr(2));
// no break here
case "r[":
case "R[":
case "t[":
case "T[":
if(!reText) {
reText = arg.substr(2);
type = arg.substr(0,1);
}
if(type == "r" || type == "t") {
try {
re = new RegExp(reText);
} catch(e) {
return results; // "forgiving mode"
}
} else
re = new RegExp(reText);
if(type == "r" || type == "R")
isBad = function(title) {
return results[i].text.match(re) ? false : true;
};
else
isBad = function(title) {
return title.match(re) ? false : true;
};
break
default:
return results;
}
// filter out corresponding tiddlers
for(var i = 0; i < results.length; i++) {
title = results[i].title;
if(isBad(title)) {
results.splice(i,1);
i--;
}
};
return results;
};
config.filters.sortByText = function (results, match) { // v1.1
// parse the argument
var arg = match[3],
ascending = +1;
switch(arg.substr(0,1)) {
case "-":
ascending = -1;
arg = arg.substr(1);
break;
case "+":
arg = arg.substr(1);
break;
}
// use the rest of the argument to get corresponding section/slice
var partSuffix = (arg.substr(0,2) == "::" || arg.substr(0,2) == "##") ?
arg : "";
var self = this;
var compareText = function(t1,t2) {
var text1 = self.getTiddlerText(t1.title + partSuffix),
text2 = self.getTiddlerText(t2.title + partSuffix);
if(text1 && text2)
return text1.localeCompare(text2);
if(text1)
return -1;
if(text2)
return 1;
return 0;
};
return results.sort(compareText);
};
orig_sortFilter = config.filters.sort;
config.filters.sort = function(results,match) { // sort: random, v1.0
if(match[3] === "*random") {
var auxiliaryArray = [],
size = results.length;
var swap = function(i, j) {
var tmp1 = auxiliaryArray[i];
auxiliaryArray[i] = auxiliaryArray[j];
auxiliaryArray[j] = tmp1;
var tmp2 = results[i];
results[i] = results[j];
results[j] = tmp2;
};
for(var i = 0; i < size; i++)
auxiliaryArray.push(Math.random());
for(var j = 0; j < size; j++)
for(var i = 0; i < size - j; i++)
if(auxiliaryArray[i] < auxiliaryArray[i+1])
swap(i,i+1);
return results;
} else
return orig_sortFilter.apply(this, arguments);
};
config.filters.from = function(results,match) { // v1.0
var filter = this.getTiddlerText(match[3],""),
tiddlers = this.filterTiddlers(filter);
for(var i = 0; i < tiddlers.length; i++)
results.pushUnique(tiddlers[i]);
return results;
};
//}}}
/***
|Description|Makes ctrl+tab work for tab changing (when focused on an editarea) in ~FireFox (instead of inserting a tab symbol)|
|Version|1.0|
|Short status|<<insertEditable container:"@stateComment" size:max cell>>|
|Author|Yakov Litvin|
keep overwriting in mind! (hope this will move to the core soon)
***/
//{{{
Story.prototype.onTiddlerKeyPress = function(ev)
{
var e = ev || window.event;
clearMessage();
var consume = false;
var title = this.getAttribute("tiddler");
var target = resolveTarget(e);
switch(e.keyCode) {
case 9: // Tab
var ed = story.getTiddlerField(title,"text");
if(target.tagName.toLowerCase() == "input" && ed.value == config.views.editor.defaultText.format([title])) {
// moving from input field and editor still contains default text, so select it
ed.focus();
ed.select();
consume = true;
}
if(config.options.chkInsertTabs && !e.ctrlKey && target.tagName.toLowerCase() == "textarea") {
replaceSelection(target,String.fromCharCode(9));
consume = true;
}
if(config.isOpera)
target.onblur = function() {
this.focus();
this.onblur = null;
};
break;
case 13: // Ctrl-Enter
case 10: // Ctrl-Enter on IE PC
case 77: // Ctrl-Enter is "M" on some platforms
if(e.ctrlKey) {
blurElement(this);
config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
consume = true;
}
break;
case 27: // Escape
blurElement(this);
config.macros.toolbar.invokeCommand(this,"cancelCommand",e);
consume = true;
break;
}
e.cancelBubble = consume;
if(consume) {
if(e.stopPropagation) e.stopPropagation(); // Stop Propagation
e.returnValue = true; // Cancel The Event in IE
if(e.preventDefault ) e.preventDefault(); // Cancel The Event in Moz
}
return !consume;
};
//}}}
/***
|Version|1.0|
{{DDnc{should be based elsewhere}}}
***/
//{{{
jQuery(window).on("scroll",function(){
var menuElement = document.getElementById("mainMenu");
if(!menuElement) return;
var header = document.getElementsByClassName("header")[0],
headerBottom = header.offsetTop + header.offsetHeight - findScrollY(),
position = findScrollY() + Math.max(headerBottom,0)
menuElement.style.top = position+"px";
});
//}}}
/***
|Description |adds toolbar buttons that allow to "fold" tiddlers|
|Version |1.0.5|
|Author |Yakov Litvin|
|Forked from |[[CollapseTiddlersPlugin|http://www.TiddlyTools.com/#CollapseTiddlersPlugin]] by Eric Shulman and previously Bradley Meck|
|Overwrites |{{{Story.prototype.permaView}}}|
|~CoreVersion? |{{DDn{??, >=2.1}}}|
***/
//{{{
config.shadowTiddlers.CollapsedTemplate =
"<!--{{{-->\
<div class='toolbar' macro='toolbar [[CollapsedToolbarCommands]]'></div>\
<div class='title' macro='view title'></div>\
<!--}}}-->";
config.shadowTiddlers.CollapsedToolbarCommands =
"closeTiddler closeOthers expandTiddler collapseOthers +editTiddler "+
"jump > fields permalink references deleteTiddler";
// hijack config.macros.toolbar.handler to add collapseTiddler and collapseOthers
config.options.chkAddFoldingButtons = (config.options.chkAddFoldingButtons === undefined) ? true : config.options.chkAddFoldingButtons;
config.foldTiddlers_orig_toolbarHandler = config.macros.toolbar.handler;
if(config.options.chkAddFoldingButtons)
config.macros.toolbar.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
// warning: paramString is not changed as /currently/ it's not used by config.macros.toolbar.handler
var i, doAdd = true,
mainCommandRegExp = /closeOthers/,
foldCommandRegExp = /collapseTiddler/,
unfoldCommandRegExp = /expandTiddler/;
// don't add in the folded condition (if expandTiddler is present)
// or if collapseTiddler is already present (manually added to toolbar)
for(i = 0; i < params.length; i++)
if(unfoldCommandRegExp.exec(params[i]) ||
foldCommandRegExp.exec(params[i])) {
doAdd = false;
break;
}
if(!doAdd)
return config.foldTiddlers_orig_toolbarHandler.apply(this,arguments);
for(i = 0; i < params.length; i++)
if(mainCommandRegExp.exec(params[i])) { // locate the "edit" command
params.splice(i+1,0,"collapseTiddler","collapseOthers"); // add after "edit"
break;
}
config.foldTiddlers_orig_toolbarHandler.apply(this,arguments);
};
//}}}
//{{{
config.commands.collapseTiddler = {
text: "fold",
tooltip: "Collapse this tiddler",
collapsedTemplate: "CollapsedTemplate",
webCollapsedTemplate: "WebCollapsedTemplate",
handler: function(event,src,title) {
var e = story.findContainingTiddler(src); if (!e) return false;
// don't fold tiddlers that are being edited!
if(story.isDirty(e.getAttribute("tiddler"))) return false;
var t = config.commands.collapseTiddler.getCollapsedTemplate();
config.commands.collapseTiddler.saveTemplate(e);
config.commands.collapseTiddler.display(title,t);
e.setAttribute("folded","true");
return false;
},
getCollapsedTemplate: function() {
return (readOnly && store.tiddlerExists(this.webCollapsedTemplate)) ?
this.webCollapsedTemplate :
this.collapsedTemplate
},
saveTemplate: function(e) {
if (e.getAttribute("savedTemplate") == undefined)
e.setAttribute("savedTemplate", e.getAttribute("template"));
},
// fold/unfold tiddler with suspend/resume of single/top/bottom-of-page mode
display: function(title,t) {
var opt = config.options;
var saveSPM = opt.chkSinglePageMode; opt.chkSinglePageMode = false;
var saveTPM = opt.chkTopOfPageMode; opt.chkTopOfPageMode = false;
var saveBPM = opt.chkBottomOfPageMode; opt.chkBottomOfPageMode = false;
story.displayTiddler(null,title,t);
opt.chkBottomOfPageMode = saveBPM;
opt.chkTopOfPageMode = saveTPM;
opt.chkSinglePageMode = saveSPM;
}
}
config.commands.expandTiddler = {
text: "unfold",
tooltip: "Expand this tiddler",
handler: function(event,src,title)
{
var e = story.findContainingTiddler(src); if (!e) return false;
var t = e.getAttribute("savedTemplate");
config.commands.collapseTiddler.display(title,t);
e.setAttribute("folded","false");
return false;
}
}
config.macros.collapseAll = {
text: "collapse all",
tooltip: "Collapse all tiddlers",
handler: function(place,macroName,params,wikifier,paramString,tiddler){
createTiddlyButton(place,this.text,this.tooltip,function(){
story.forEachTiddler(function(title,tiddler)
{
if(story.isDirty(title)) return;
var t = config.commands.collapseTiddler.getCollapsedTemplate();
config.commands.collapseTiddler.saveTemplate(tiddler);
config.commands.collapseTiddler.display(title,t);
tiddler.folded = true;
});
return false;
})
}
}
config.macros.expandAll = {
text: "expand all",
tooltip: "Expand all tiddlers",
handler: function(place,macroName,params,wikifier,paramString,tiddler){
createTiddlyButton(place,this.text,this.tooltip,function(){
story.forEachTiddler(function(title,tiddler){
var t = config.commands.collapseTiddler.getCollapsedTemplate();
if(tiddler.getAttribute("template")!=t) return; // re-display only if collapsed
var t = tiddler.getAttribute("savedTemplate");
config.commands.collapseTiddler.display(title,t);
tiddler.folded = false;
});
return false;
})
}
}
config.commands.collapseOthers = {
text: "focus",
tooltip: "Expand this tiddler and collapse all others",
handler: function(event,src,title) {
var e = story.findContainingTiddler(src); if (!e) return false;
story.forEachTiddler(function(title,tiddler) {
if(story.isDirty(title)) return;
var t = config.commands.collapseTiddler.getCollapsedTemplate();
if (e == tiddler) t = e.getAttribute("savedTemplate");
config.commands.collapseTiddler.saveTemplate(tiddler);
config.commands.collapseTiddler.display(title,t);
tiddler.folded = (e != tiddler);
})
return false;
}
}
// {{{<<foldFirst>>}}} macro forces tiddler to be folded when *initially* displayed.
// Subsequent re-render does NOT re-fold tiddler, but closing/re-opening tiddler DOES cause it to fold first again.
config.macros.foldFirst = {
handler: function(place,macroName,params,wikifier,paramString,tiddler) {
var e = story.findContainingTiddler(place);
if (e.getAttribute("foldedFirst") == "true") return; // already been folded once
var title = e.getAttribute("tiddler")
var t = config.commands.collapseTiddler.getCollapsedTemplate();
config.commands.collapseTiddler.saveTemplate(e);
config.commands.collapseTiddler.display(title,t);
e.setAttribute("folded","true");
e.setAttribute("foldedFirst","true"); // only when tiddler is first rendered
return false;
}
}
//}}}
//{{{
// [showFolded[n]] adds .openFolded = true to the last n tiddlers among results
config.filters.showFolded = function(results,match) {
var number = parseInt(match[3]); // if NaN, the loop below just is not executed
for(var i = 0; (i < number) && (results.length-1-i >= 0); i++)
results[results.length-1-i].openFolded = true;
return results;
}
// hijack chooseTemplateForTiddler to open tiddlers with .openFolded folded
config.foldTiddlers_orig_chooseTemplateForTiddler = Story.prototype.chooseTemplateForTiddler;
Story.prototype.chooseTemplateForTiddler = function(title,template) {
var t = store.fetchTiddler(title);
if(t && t.openFolded)
{
t.openFolded = false
return "CollapsedTemplate";
}
return config.foldTiddlers_orig_chooseTemplateForTiddler.apply(this,arguments);
}
//# optionally change fold/unfold ~commands so that they change openFolded (the state is remembered during the session)
Story.prototype.isOpenedFolded = function(title) {
var tiddlerElem = story.getTiddler(title);
if(!tiddlerElem)
return false;
return tiddlerElem.getAttribute("template") == "CollapsedTemplate";
}
//# overwrite permaview to save which tiddlers are folded
// use "filter" paramifier
Story.prototype.permaView = function()
{
var links = [];
this.forEachTiddler(function(title,element) {
links.push(String.encodeTiddlyLink(title) +
(story.isOpenedFolded(title) ? " [showFolded[1]]" : ""));
});
var t = links.join(" ");
t = t.replace(/\\/gm,"\\\\").replace(/"/gm,'\\"');
if(t == "")
t = "#";
else
t = encodeURIComponent('filter:"'+t+'"');
if(window.location.hash != t)
window.location.hash = t;
};
// unescape \" and \\ back..
var _unescape = function(value) {
if(typeof value == "string")
return value.replace(/\\"/gm,'"').replace(/\\\\/,'\\');
return value;
};
// ..by hijacking invokeParamifier (certain paramifiers may be hijacked instead - probably enough to hijack only the "filter" one)
config.extensions.ftp_orig_invokeParamifier = invokeParamifier;
invokeParamifier = function(params,handler) {
if(!params)
return config.extensions.ftp_orig_invokeParamifier.apply(this,arguments);
var paramifiers = params[0], name, i;
for(name in paramifiers)
for(i = 0; i < paramifiers[name].length; i++)
paramifiers[name][i] = _unescape(paramifiers[name][i]);
for(i = 1; i < params.length; i++)
params[i].value = _unescape(params[i].value);
return config.extensions.ftp_orig_invokeParamifier.apply(this,arguments);
};
//}}}
/***
|''Name''|ForEachTiddlerPlugin|
|''Version''|1.3.1|
|''Forked from''|[[abego.ForEachTiddlerPlugin|http://tiddlywiki.abego-software.de/#ForEachTiddlerPlugin]], by Udo Borkowski|
|''Author''|Yakov Litvin|
|''CoreVersion''|2.6.2|
|~|Although 2.6.2 is theoretically minimal TW version required for the correct operation, tests showed that the plugin works in 2.6.0, too.|
***/
//{{{
(function(){
// Only install once
if (version.extensions.ForEachTiddlerPlugin) {
alert("Warning: more than one copy of ForEachTiddlerPlugin is set to be launched");
return;
} else
version.extensions.ForEachTiddlerPlugin = {
source: "[repository url here]",
licence: "[licence url here]",
copyright: "Copyright (c) Yakov Litvin, 2012-2015 [url of the meta page]"
};
//============================================================================
// forEachTiddler Macro
//============================================================================
// ---------------------------------------------------------------------------
// Configurations and constants
// ---------------------------------------------------------------------------
config.macros.forEachTiddler =
{
actions: {
addToList: {},
write: {}
}
};
// ---------------------------------------------------------------------------
// The forEachTiddler Macro Handler
// ---------------------------------------------------------------------------
config.macros.forEachTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
// --- Pre-parsing for up-to-date params ----------------
var preParsedParams = this.getUpToDateParams(paramString);
// for backward compability, "params" are used as well
// --- Parsing ------------------------------------------
var parsedParams = this.parseParams(preParsedParams,params);
if (parsedParams.errorText) {
this.handleError(place, parsedParams.errorText);
return;
}//else
parsedParams.place = place;
parsedParams.inTiddler = tiddler ? tiddler : getContainingTiddler(place);
// --- "Static" processing ------------------------------
// Choose the action
var actionName = parsedParams.actionName;
var action = this.actions[actionName]; // no this is always a "known" action
// Create the element
var element = document.createElement(action.element);
jQuery(element).attr({ refresh: "macro", macroName: macroName }).data(parsedParams);
place.appendChild(element);
// --- "Dynamic" processing -----------------------------
this.refresh(element);
};
config.macros.forEachTiddler.refresh = function(element)
{
var parsedParams = jQuery(element).data(),
action = this.actions[parsedParams.actionName];
jQuery(element).empty();
try {
var tiddlersAndContext = this.getTiddlersAndContext(parsedParams);
// Perform the action
action.handler(element, tiddlersAndContext.tiddlers,
parsedParams.actionParameter, tiddlersAndContext.context);
} catch (e) {
this.handleError(place, e);
}
};
config.macros.forEachTiddler.oldFashionParams = ["in", "filter", "where", "sortBy",
"script", "write", "begin", "end", "none", "toFile", "withLineSeparator"
//# add to docs: new actions are to be added here or used in name:param notation only
];
config.macros.forEachTiddler.getUpToDateParams = function(paramString)
// turns stuff like "... where 'tiddler.title.length < 20' ..."
// to "... where:'tiddler.title.length < 20' ..." and then applies parseParams,
// which allows to use params in an arbitrary order and other goodies of parsed params
{
var paramPairRegExp = new RegExp("("+this.oldFashionParams.join("|")+")\\s+"+
"("+ // adapted from String.prototype.parseParams
'(?:"(?:(?:\\\\")|[^"])+")|'+ // double-quoted param
"(?:'(?:(?:\\\\')|[^'])+')|"+ // quoted param
"(?:\\[\\[(?:\\s|\\S)*?\\]\\])|"+ // [[...]]-wrapped
"(?:\\{\\{(?:\\s|\\S)*?\\}\\})|"+ // {{...}}-wrapped
"(?:[^\"':\\s][^\\s:]*)|"+ // non-wrapped
"(?:\"\")|(?:'')"+ // empty '' or ""
")","g");
paramString =
paramString.replace(paramPairRegExp,function($0,$1,$2){ return $1+":"+$2; });
return paramString.parseParams("filter",null,true,false,true);
// the first unnamed param is now considered as the 'filter' param
};
// Returns an object with properties "tiddlers" and "context".
// tiddlers holds the (sorted) tiddlers selected by the parameter,
// context the context of the execution of the macro.
//
// The action is not yet performed.
//
// @param parameter holds the parameter of the macro as separate properties.
// The following properties are supported:
//
// place
// filter
// whereClause
// sortClause
// sortAscending
// actionName
// actionParameter
// scriptText
// tiddlyWikiPath
//
// All properties are optional.
// For most actions the place property must be defined.
//
config.macros.forEachTiddler.getTiddlersAndContext = function(parameter)
{
var context = config.macros.forEachTiddler.createContext(parameter.place, parameter.filter, parameter.whereClause, parameter.sortClause, parameter.sortAscending, parameter.actionName, parameter.actionParameter, parameter.scriptText, parameter.tiddlyWikiPath, parameter.inTiddler);
var tiddlyWiki = parameter.tiddlyWikiPath ? this.loadTiddlyWiki(parameter.tiddlyWikiPath) : store;
context["tiddlyWiki"] = tiddlyWiki;
// Get the tiddlers, as defined by the filter and the whereClause
var tiddlers = this.findTiddlers(parameter.filter, parameter.whereClause, context, tiddlyWiki);
context["tiddlers"] = tiddlers;
// Sort the tiddlers, when sorting is required.
if (parameter.sortClause)
this.sortTiddlers(tiddlers, parameter.sortClause, parameter.sortAscending, context);
return {tiddlers: tiddlers, context: context};
};
// ---------------------------------------------------------------------------
// The actions
// ---------------------------------------------------------------------------
// Internal.
//
// --- The addToList Action -----------------------------------------------
//
config.macros.forEachTiddler.actions.addToList.element = "ul";
config.macros.forEachTiddler.actions.addToList.handler = function(place, tiddlers, parameter, context)
{
for (var i = 0; i < tiddlers.length; i++)
{
var tiddler = tiddlers[i];
var listItem = document.createElement("li");
place.appendChild(listItem);
createTiddlyLink(listItem, tiddler.title, true);
}
};
// Internal.
//
// --- The write Action ---------------------------------------------------
//
config.macros.forEachTiddler.actions.write.element = "span";
config.macros.forEachTiddler.actions.write.handler = function(place, tiddlers, parameter, context)
{
var params = parameter[0].nonParsedParams;
if(!parameter[0]["write"])
return this.handleError(place, "Missing expression behind 'write'.");
var textExpression = config.macros.forEachTiddler.paramEncode(getParam(parameter,["write"]));
var getParamExpression = function(name)
{
if(params.contains(name) && !parameter[0][name])
throw "Missing text behind '%0'".format([name]);
return config.macros.forEachTiddler.paramEncode(getParam(parameter,name));
};
var beginExpression = getParamExpression("begin");
var endExpression = getParamExpression("end");
var noneExpression = getParamExpression("none");
var lineSeparator = undefined;
if(params.contains("toFile") && !parameter[0]["toFile"])
return this.handleError(place, "Filename expected behind 'toFile' of 'write' action.");
var filename = getParam(parameter,"toFile");
filename = config.macros.forEachTiddler.paramEncode(filename);
if(filename) {
filename = config.macros.forEachTiddler.getLocalPath(filename);
if(params.contains("withLineSeparator")&& !parameter[0]["withLineSeparator"])
return this.handleError(place, "Line separator text expected behind 'withLineSeparator' of 'write' action.")
lineSeparator = getParamExpression("withLineSeparator");
}
// Perform the action.
var func = config.macros.forEachTiddler.getEvalTiddlerFunction(textExpression, context),
count = tiddlers.length,
text = "";
if (count > 0 && beginExpression)
text += config.macros.forEachTiddler.getEvalTiddlerFunction(beginExpression, context)(undefined, context, count, undefined);
for (var i = 0; i < count; i++) {
var tiddler = tiddlers[i];
text += func(tiddler, context, count, i);
}
if (count > 0 && endExpression)
text += config.macros.forEachTiddler.getEvalTiddlerFunction(endExpression, context)(undefined, context, count, undefined);
if (count == 0 && noneExpression)
{
var beginAddition = beginExpression ? "("+beginExpression+")" : "",
endAddition = endExpression ? "("+ endExpression+")" : "",
bothAddition = "("+beginAddition
+((beginAddition && endAddition) ? "+" : "")+endAddition+")";
noneExpression = noneExpression
.replace(/(?=\W|^)begin(?=\W|$)/,beginAddition)
.replace(/(?=\W|^)end(?=\W|$)/, endAddition)
.replace(/(?=\W|^)same(?=\W|$)/, bothAddition);
text += config.macros.forEachTiddler.getEvalTiddlerFunction(noneExpression, context)(undefined, context, count, undefined);
}
if (filename) {
if (lineSeparator !== undefined) {
lineSeparator = lineSeparator.replace(/\\n/mg, "\n").replace(/\\r/mg, "\r");
text = text.replace(/\n/mg,lineSeparator);
}
saveFile(filename, convertUnicodeToUTF8(text));
} else
wikify(text, place, null/* highlightRegExp */, context.inTiddler);
};
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
config.macros.forEachTiddler.parseParams = function(preParsedParams,params)
{
if(params.contains("in") && !preParsedParams[0]["in"])
return { errorText: "TiddlyWiki path expected behind 'in'." };
var TWpath = getParam(preParsedParams,"in");
if(params.contains("filter") && !preParsedParams[0]["filter"])
return { errorText: "No filter specified." };
if(params.contains("where") && !preParsedParams[0]["where"])
return { errorText: "whereClause missing behind 'where'." };
var where = getParam(preParsedParams,"where");
var ascending = true;
if(params.contains("sortBy") && !preParsedParams[0]["sortBy"])
return { errorText: "sortClause missing behind 'sortBy'." };
var sortClause = getParam(preParsedParams,"sortBy");
if(preParsedParams[0]["sortBy"] && preParsedParams[0]["sortBy"].length > 1)
ascending = !(preParsedParams[0]["sortBy"][1] == "descending");
if(params.contains("script") && !preParsedParams[0]["script"])
return { errorText: "scriptText is not specified." };
var scriptText = getParam(preParsedParams,"script");
var actionName = "addToList";
for(var knownActionName in this.actions)
if(preParsedParams[0][knownActionName]) {
actionName = knownActionName;
break;
}
// no error handling if there's an unknown action
// because now the order is not important and actionName can have another position
preParsedParams[0].nonParsedParams = params; // for parsing inside actions
return {
tiddlyWikiPath: this.paramEncode(TWpath),
filter: getParam(preParsedParams,"filter"),
whereClause: this.paramEncode(where) || true,
sortClause: this.paramEncode(sortClause),
sortAscending: ascending,
scriptText: this.paramEncode(scriptText),
actionName: actionName,
actionParameter:preParsedParams // not much need to cut out other params
}
};
var getContainingTiddler = function(e)
{
while(e && !hasClass(e,"tiddler"))
e = e.parentNode;
var title = e ? e.getAttribute("tiddler") : null;
return title ? store.getTiddler(title) : null;
};
// Internal.
//
config.macros.forEachTiddler.createContext = function(placeParam, filterParam, whereClauseParam, sortClauseParam, sortAscendingParam, actionNameParam, actionParameterParam, scriptText, tiddlyWikiPathParam, inTiddlerParam) {
return {
place : placeParam,
filter : filterParam,
whereClause : whereClauseParam,
sortClause : sortClauseParam,
sortAscending : sortAscendingParam,
script : scriptText,
actionName : actionNameParam,
actionParameter : actionParameterParam,
tiddlyWikiPath : tiddlyWikiPathParam,
inTiddler : inTiddlerParam, // the tiddler containing the <<forEachTiddler ...>> macro call.
viewerTiddler : getContainingTiddler(placeParam) //the tiddler showing the forEachTiddler result
};
};
// Internal.
//
// Returns a TiddlyWiki with the tiddlers loaded from the TiddlyWiki of the given path.
//
config.macros.forEachTiddler.loadTiddlyWiki = function(path, idPrefix)
{
if (!idPrefix)
idPrefix = "store";
var lenPrefix = idPrefix.length;
// Read the content of the given file
var content = loadFile(this.getLocalPath(path));
if(content === null)
throw "TiddlyWiki '"+path+"' not found.";
var tiddlyWiki = new TiddlyWiki();
if (!tiddlyWiki.importTiddlyWiki(content))
throw "File '"+path+"' is not a TiddlyWiki.";
tiddlyWiki.dirty = false;
return tiddlyWiki;
};
// Internal.
//
// Returns a function that has a function body returning the given javaScriptExpression.
// The function has the parameters:
//
// (tiddler, context, count, index)
//
config.macros.forEachTiddler.getEvalTiddlerFunction = function (javaScriptExpression, context) {
var script = context["script"];
// var functionText = "var theFunction = function(tiddler, context, count, index) { return "+javaScriptExpression+"}";
var functionText = "var theFunction = function(tiddler, context, count, index) { "+(script ? script+";" : "")+"return "+javaScriptExpression+"}";
// var fullText = (script ? script+";" : "")+functionText+";theFunction;";
var fullText = functionText+";theFunction;";
return eval(fullText);
};
// Internal.
//
config.macros.forEachTiddler.findTiddlers = function(filter, whereClause, context, tiddlyWiki) {
var result = [];
var func = config.macros.forEachTiddler.getEvalTiddlerFunction(whereClause, context);
if(filter) {
var tids = tiddlyWiki.filterTiddlers(filter);
for(var i = 0; i < tids.length; i++)
if(func(tids[i], context, undefined, undefined))
result.push(tids[i]);
} else
tiddlyWiki.forEachTiddler(function(title,tiddler) {
if(func(tiddler, context, undefined, undefined))
result.push(tiddler);
});
return result;
};
// Internal.
//
config.macros.forEachTiddler.sortAscending = function(tiddlerA, tiddlerB)
{
return ((tiddlerA.forEachTiddlerSortValue == tiddlerB.forEachTiddlerSortValue)
? 0
: ((tiddlerA.forEachTiddlerSortValue < tiddlerB.forEachTiddlerSortValue)
? -1
: +1))
};
// Internal.
//
config.macros.forEachTiddler.sortDescending = function(tiddlerA, tiddlerB)
{
return ((tiddlerA.forEachTiddlerSortValue == tiddlerB.forEachTiddlerSortValue)
? 0
: ((tiddlerA.forEachTiddlerSortValue < tiddlerB.forEachTiddlerSortValue)
? +1
: -1))
};
// Internal.
//
config.macros.forEachTiddler.sortTiddlers = function(tiddlers, sortClause, ascending, context) {
// To avoid evaluating the sortClause whenever two items are compared
// we pre-calculate the sortValue for every item in the array and store it in a
// temporary property ("forEachTiddlerSortValue") of the tiddlers.
var func = config.macros.forEachTiddler.getEvalTiddlerFunction(sortClause, context);
var count = tiddlers.length;
var i;
for (i = 0; i < count; i++) {
var tiddler = tiddlers[i];
tiddler.forEachTiddlerSortValue = func(tiddler,context, undefined, undefined);
}
// Do the sorting
tiddlers.sort(ascending ? this.sortAscending : this.sortDescending);
// Delete the temporary property that holds the sortValue.
for (i = 0; i < tiddlers.length; i++)
delete tiddlers[i].forEachTiddlerSortValue;
};
// Internal.
//
// Creates an element that holds an error message
//
config.macros.forEachTiddler.createErrorElement = function(place, exception) {
var message = (exception.description) ? exception.description : exception.toString();
return createTiddlyElement(place,"span",null,"forEachTiddlerError","<<forEachTiddler ...>>: "+message);
};
// Internal.
//
// @param place [may be null]
//
config.macros.forEachTiddler.handleError = function(place, exception)
{
if(place)
this.createErrorElement(place, exception);
else
throw exception;
};
// Internal.
//
// Encodes the given string.
//
// Replaces
// "$))" to ">>"
// "$)" to ">"
//
config.macros.forEachTiddler.paramEncode = function(s)
{
if(!s) return s;
var reGTGT = new RegExp("\\$\\)\\)","mg");
var reGT = new RegExp("\\$\\)","mg");
return s.replace(reGTGT, ">>").replace(reGT, ">");
};
//# document the .paramEncode transformation of the params; or get rid of it?
// Internal.
//
// Returns the given original path (that is a file path, starting with "file:")
// as a path to a local file, in the systems native file format.
//
// Handles relative links, too.
//
config.macros.forEachTiddler.getLocalPath = function(originalPath) {
// code adapted from SharedTiddlersPlugin to handle relative paths
var originalAbsolutePath = originalPath;
if(originalAbsolutePath.search(/^((http(s)?)|(file)):/) != 0) {
// no protocol prefix..
if (originalAbsolutePath.search(/^(.\:\\)|(\\\\)|(\/)/) != 0){// is relative?
// as Unix filesystem root is "/", urls starting with it are not considered as relative
var currentUrl = document.location.toString();
var currentPath = (currentUrl.lastIndexOf("/") > -1) ?
currentUrl.substr(0, currentUrl.lastIndexOf("/") + 1) :
currentUrl + "/";
originalAbsolutePath = currentPath + originalAbsolutePath;
} else
// an "absolute" path to a local file. Prefix it with file://
originalAbsolutePath = "file://" + originalAbsolutePath;
// replace every \ by a /, to cover Windows style pathes
originalAbsolutePath = originalAbsolutePath.replace(/\\/mg,"/");
}
return getLocalPath(originalAbsolutePath);
};
// ---------------------------------------------------------------------------
// Stylesheet Extensions (may be overridden by local StyleSheet)
// ---------------------------------------------------------------------------
//
setStylesheet(
".forEachTiddlerError{color: #ffffff;background-color: #880000;}",
"forEachTiddler");
// ---------------------------------------------------------------------------
// fet alias for the the forEachTiddler Macro
// ---------------------------------------------------------------------------
config.macros.fet = config.macros.forEachTiddler;
//============================================================================
// utilities for String and Tiddler objects useful in fet macros
//============================================================================
// Returns true if the string starts with the given prefix, false otherwise.
//
String.prototype.startsWith = function(prefix) {
var n = prefix.length;
return (this.length >= n) && (this.slice(0, n) == prefix);
};
// Returns true if the string ends with the given suffix, false otherwise.
//
String.prototype.endsWith = function(suffix) {
var n = suffix.length;
return (this.length >= n) && (this.right(n) == suffix);
};
// Returns true when the string contains the given substring, false otherwise.
//
String.prototype.contains = function(substring) {
return this.indexOf(substring) >= 0;
};
})();
// Returns the slice value if it is present or defaultText otherwise
//
Tiddler.prototype.getSlice = function(sliceName,defaultText)
{
var re = TiddlyWiki.prototype.slicesRE;
re.lastIndex = 0;
var m = re.exec(this.text);
while(m) {
if(m[2]) {
if(m[2] == sliceName)
return m[3];
} else {
if(m[5] == sliceName)
return m[6];
}
m = re.exec(this.text);
}
return defaultText;
};
// Returns the section value if it is present or defaultText otherwise
//
Tiddler.prototype.getSection = function(sectionName,defaultText)
{
var beginSectionRegExp = new RegExp("(^!{1,6}[ \t]*" + sectionName.escapeRegExp() + "[ \t]*\n)","mg"),
sectionTerminatorRegExp = /^!/mg;
var match = beginSectionRegExp.exec(this.text), sectionText;
if(match) {
sectionText = this.text.substr(match.index+match[1].length);
match = sectionTerminatorRegExp.exec(sectionText);
if(match)
sectionText = sectionText.substr(0,match.index-1); // don't include final \n
return sectionText;
}
return defaultText;
};
var transText = function(tiddlerOrGetTiddlerTextArg, moreArguments)
{
var title = (tiddlerOrGetTiddlerTextArg instanceof Tiddler) ? tiddlerOrGetTiddlerTextArg.title : tiddlerOrGetTiddlerTextArg;
return "<<tiddler [[" + title + "]] "+ (moreArguments||"") +">>"
};
//}}}
/***
|''Name''|FromPlaceToPlacePlugin|
|''Description''|allows to substitute current tiddlers or page instead of opening tiddlers/pages in addition|
|''Documentation''|see below|
|''Type''|plugin|
|''Version''|1.2.0|
|''CoreVersion''|2.5.0|
|''Source''|http://yakovl.bplaced.net/TW/FPTP.html#FromPlaceToPlacePlugin|
|''Author''|Yakov Litvin|
|''Contact''|See the [[main plugin discussion thread|https://groups.google.com/forum/#!topic/tiddlywiki/bICRWy8qo8g]] and [[contacts|http://customize.tiddlyspace.com/#%5B%5BYakov%20Litvin%5D%5D]]|
|''Copyright''|Yakov Litvin, 2013|
|''Licence''|[[BSD-like open source license|http://yakovl.bplaced.net/TW/FPTP.html#%5B%5BYakov%20Litvin%20Public%20Licence%5D%5D]]|
!!!Introduction
In ~TiddlyWiki, links work "comulatively": when you click an internal link, you get +1 tiddler opened, external links open pages without closing ~TiddlyWiki (hence +1 browser tab). At times, this causes unnecessary "flooding" with opened things (tiddlers/pages). To solve this, FromPlaceToPlacePlugin was created.
It works in a simple way: it keeps the common functionality of the "click a link" action, but "hold meta key + click a link" causes "close and open" action:
* for internal links, this means "close the tiddler in which the link is placed and open the target tiddler"
* for external links, this means "open the page in the same browser tab"
!!!Installation & usage
Aside the usual import/copy-and-add-{{{systemConfig}}}-tag action, you need to adjust the meta keys for internal and external links (and reload afterwards). To do this, change the "Config" section of this tiddler ({{{txtFromTiddlerToTiddlerKey}}} for internal and {{{txtFromPageToPageKey}}} for external links), if necessary. Note that:
* {{{shift}}} doesn't work well for external links in Opera: on shift+click it opens the link in a new tab, so this will result in two equal tabs opened (with Opera, I recommend {{{alt}}})
* {{{alt}}} doesn't work well with IE, so you probably would prefer {{{shift}}}
* each {{{alt}}}, {{{ctrl}}} and {{{shift}}} work (with the limitations above); any other value of an option deactivates corresponding feature
Once the meta keys are set and TW is reloaded, try to click links..
!!!Demo
* click this [[internal link|Introduction to FromPlaceToPlacePlugin]] while pressing {{{shift}}} (or whatever meta key you've set)
* click this [[external link|http://yakovl.bplaced.net/TW/STP/STP.html]] while holding {{{alt}}} key
!!!Additional notes
* this works even with implicit links (like those in the "references" popup)
* "external links" are links with the {{{externalLink}}} class, so links created with inline-html won't work unless the class is added
!!!Config
***/
//{{{
config.extensions.txtFromPageToPageKey = 'alt'; // each 'alt', 'ctrl' and 'shift' work
config.extensions.txtFromTiddlerToTiddlerKey = 'shift'; // each 'alt', 'ctrl' and 'shift' work
//}}}
/***
!!!Code
***/
//{{{
(function(){
if(version.extensions.FromPlaceToPlacePlugin)
return;
version.extensions.FromPlaceToPlacePlugin = { major: 1, minor: 2, revision: 0, date: new Date(2013,10,24)};
var firedWhenKeyWasPressed = function(event,key) {
return (event.shiftKey && key == 'shift') ||
(event.ctrlKey && key == 'ctrl') ||
(event.altKey && key == 'alt');
}
//------------------------------------------------------------------------------------------------------------
// From tiddler to tiddler
// keep as a global variable for a possibility of introspection
orig_onClickTiddlerLink = onClickTiddlerLink;
// hijack
onClickTiddlerLink = function(ev) {
var result,
sourceTid = story.findContainingTiddler(this),
event = ev || window.event,
key = config.extensions.txtFromTiddlerToTiddlerKey,
close = (firedWhenKeyWasPressed(event,key) && sourceTid) ? true : false;
// to "correct" page and zoomer position,
// hide the "source" tiddler before opening the "target" and closing the "source"
if(close)
sourceTid.style.display = "none";
result = orig_onClickTiddlerLink(event);
if(close) {
var tName = sourceTid.getAttribute("tiddler");
story.closeTiddler(tName);
}
return result;
}
//------------------------------------------------------------------------------------------------------------
// From page to page
jQuery("body").delegate("a.externalLink", "click", function(ev) {
var event = ev || window.event,
key = config.extensions.txtFromPageToPageKey,
close = firedWhenKeyWasPressed(event,key),
target = jQuery(this).attr("href");
if(close) {
window.location.assign(target);
return false;
}
});
})();
//}}}
/***
|Version|custom version forked from 0.4.5|
|Requires|FoldTiddlerPlugin CustomSettingsPlugin|
|~|doesn't require FoldTiddlerPlugin, but if used, that one must be launched first|
***/
//{{{
var permaViewClass = "permaviewButton";
config.options.chkSinglePageMode = readOnly;
config.options.chkKeepPermaview = readOnly;
config.macros.reopenPermaview =
{
reopen: function()
{
var params = getParameters(); // paramifiers in hash
story.closeAllTiddlers();
if(!params)
story.displayDefaultTiddlers();
else {
params = params.parseParams("open",null,false);
invokeParamifier(params,"onstart");
}
},
handler: function(place,macroName,params,wikifier,paramString)
{
// parse params
var pParams = paramString.parseParams("label",null,true,false,true),
label = getParam(pParams,"label","⟳"),
prompt = getParam(pParams,"prompt","reopen permaview/default tiddlers");
// create button
createTiddlyButton(place,label,prompt,this.reopen);
}
};
// extend the permaview macro:
// when chkAddReopenToPermaview is true, add the reopen button next to the permaview
// also add the permaviewButton class to display the "non-permaview" state
config.macros.permaview.HPVP_orig_handler = config.macros.permaview.handler;
config.macros.permaview.handler = function(place,macroName,params,wikifier,paramString,tid)
{
if(config.options.chkAddReopenToPermaview) {
var wrapper = createTiddlyElement(place,"span");
place = wrapper;
}
this.HPVP_orig_handler.apply(this,arguments);
place.lastChild.classList.add(permaViewClass);
if(config.options.chkAddReopenToPermaview) {
place.lastChild.style.display = "inline";
config.macros.reopenPermaview.handler(place,"",[],wikifier,"");
place.lastChild.style.display = "inline";
// inline styling is needed for the sidebar
}
};
// keep permaview on any tiddler open/close:
story.HPVP_orig_displayTiddler = story.displayTiddler;
story.displayTiddler = function(srcElement,tiddler,template,animate,unused,customFields,toggle,animationSrc)
{
if(config.options.chkSinglePageMode)
{
var savedPermaView = this.permaView;
this.permaView = function(){}; // prevent intermediate "empty hash" state
story.closeAllTiddlers();
}
var result = this.HPVP_orig_displayTiddler.apply(this,arguments),
title = (tiddler instanceof Tiddler) ? tiddler.getVisibleName() : tiddler;
if(config.options.chkSinglePageMode) {
this.permaView = savedPermaView;
document.title = title.replace(/ /gm," ") +" - "+ store.getTiddlerText("SiteSubtitle");
}
if(config.options.chkKeepPermaview)
this.permaView();
if(config.options.chkSinglePageMode) window.scroll(0,0);
//# scroll to the top of #tiddlerDisplay , take animation into account
return result;
};
story.HPVP_orig_closeTiddler = story.closeTiddler;
story.closeTiddler = function(title,animate,unused)
{
var result = story.HPVP_orig_closeTiddler.apply(this,arguments);
if(config.options.chkKeepPermaview) {
if(config.options.chkAnimate)
// animated closing keeps tiddler visible for .permaView() for some time
setTimeout("story.permaView()",config.animDuration+10);
else
// delay without animation causes "sometimes fail to close" bug
// (ensureOpenedByURI fires earlier)
story.permaView();
}
return result;
};
// enable "back" button (both browser and keyboard) by regularly checking permaview
var ensureOpenedByURI = function()
{
if(!config.options.chkKeepPermaview) return;
var correctTiddlersAreOpen = true,
paramifiersString = getParameters(), paramifiers, filter, tiddlers,
exoticParamifiersPresent = false, i, currentlyOpenTiddlers = [];
// check if some tiddlers are missing/extra compared to permaview
if(paramifiersString) {
paramifiers = paramifiersString.parseParams("open",null,false);
filter = "";
// paramifiers[0] is a "summary" object, skip that one
for(i = 1; i < paramifiers.length; i++)
switch(paramifiers[i].name) {
// since permaview only generates either filter or open we only take them into account
case "filter": filter += paramifiers[i].value; break;
case "open": filter += "[["+paramifiers[i].value+"]]"; break;
default: exoticParamifiersPresent = true;
}
} else
filter = store.getTiddlerText("DefaultTiddlers");
if(exoticParamifiersPresent) return;
// compare the open tiddler set and the set suggested by paramifiers
tiddlers = store.filterTiddlers(filter);
story.forEachTiddler(function(title,e){currentlyOpenTiddlers.push(title);});
if(tiddlers.length != currentlyOpenTiddlers.length)
correctTiddlersAreOpen = false;
else
for(i = 0; i < tiddlers.length; i++)
if(currentlyOpenTiddlers.indexOf(tiddlers[i].title) == -1)
correctTiddlersAreOpen = false;
// save scroll position
var posX = findScrollX();
var posY = findScrollY();
// reopen if none are edited
if(!correctTiddlersAreOpen && !story.areAnyDirty()) {
config.macros.reopenPermaview.reopen();
window.scrollTo(posX,posY);
}
}
setInterval(ensureOpenedByURI,300);
//# see also https://css-tricks.com/using-the-html5-history-api/ , "ajax browser history html5"
// pragmatics: permalinks well-distinguished by social networks
// apply different styling to the permaview button, depending on permaview/"non-permaview" state
var css = "."+permaViewClass+" { font-weight: bold; color: red !important; }";
//# trace displayed stuff, on story change apply styles to .permaviewButton
// setStylesheet(css,"highlightSaving");
//# on permaview, change the styles back
// removeStyleSheet("highlightSaving");
//}}}
/***
|Description|This plugin introduces new formatters that generate sub- and superscripts|
|Version|1.0|
|Short status|<<insertEditable container:"@stateComment" size:max cell>>|
subscript syntax: {{{text,,with subscript,,}}} → text,,with subscript,,
superscript containing float syntaxes:
{{{10^1.1}}} → 10^1.1, {{{10^+1,1}}} → 10^+1,1, {{{10^-0.2}}} → 10^-0.2, {{{2^8}}} → 2^8, {{{n^3}}} → n^3
***/
//{{{
config.formatters.push({
name: "subscript",
match: ",,(?!\\s|$)",
handler: function(w) {
w.subWikifyTerm(createTiddlyElement(w.output,"sub"),/(,,)/mg);
}
},{
name: "power",
match: "\\^(?:\\+|-|±|\\+-|∓|-\\+)?\\d+(?:(?:\\.|,)\\d+)?",
lookaheadRegExp: /\^((?:\+|-|±|\+-|∓|-\+)?\d+(?:(?:\.|,)\d+)?)/g,
handler: function(w) {
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source),
power = lookaheadMatch[1].replace("+-","±").replace("-+","∓");
createTiddlyElement(w.output,'sup',null,null/*class*/,power);
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
})
//}}}
/***
тестируем в [[test bootstrap]]
***/
//{{{
// using actual link elements in the template works much better as CSS is applied only in that contex
//jQuery("head").append("<link rel='stylesheet' type='text/css' href='css/bootstrap.min.css' />");
//jQuery("head").append("<link rel='stylesheet' type='text/css' href='css/course.css' />");
/*
//# use jQuery.getScript("js/bootstrap.min.js") on "test bootstrap" tiddler open
story.bs_orig_displayTiddler = story.displayTiddler;
story.displayTiddler = function (srcElement,tiddler,template,animate,unused,customFields,toggle,animationSrc)
{
var res = this.bs_orig_displayTiddler.apply(this,arguments);
var title = (tiddler instanceof Tiddler) ? tiddler.title : tiddler;
if(title == "test bootstrap")
jQuery.getScript("js/bootstrap.min.js");
return res;
};
*/
//}}}
/***
1. loading bs.js in MarkupPostBody via {{{<script src="js/bootstrap.min.js"></script>}}} doesn't work: looks like it must be launched when the DOM is built already
retest js, notes below may be a misinterpretation (it is CSS which was missing):
2. [[loading|LoadBootstrapPlugin]] bs.js async on tiddler open doesn't work "as expected" as well!
----
3. ???
4. check what happens if bs.js is loaded twice?
5. check if bs.js has to be loaded each time the tiddler is reopened after closing (try to just reopen first)
6. check how 2+ bs.js interact
***/
/***
|Name|NestedSlidersPlugin|
|Source|http://www.TiddlyTools.com/#NestedSlidersPlugin|
|Documentation|http://www.TiddlyTools.com/#NestedSlidersPluginInfo|
|Version|2.4.9|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|show content in nest-able sliding/floating panels, without creating separate tiddlers for each panel's content|
!!!!!Configuration
<<<
<<option chkFloatingSlidersAnimate>> allow floating sliders to animate when opening/closing
Note: for floating slider animation to occur you must also allow animation in general (see [[AdvancedOptions]]).
<<<
!!!!!Code
***/
//{{{
version.extensions.NestedSlidersPlugin= {major: 2, minor: 4, revision: 9, date: new Date(2008,11,15)};
// options for deferred rendering of sliders that are not initially displayed
if (config.options.chkFloatingSlidersAnimate===undefined)
config.options.chkFloatingSlidersAnimate=false; // avoid clipping problems in IE
// default styles for 'floating' class
setStylesheet(".floatingPanel { position:absolute; z-index:10; padding:0.5em; margin:0em; \
background-color:#eee; color:#000; border:1px solid #000; text-align:left; }","floatingPanelStylesheet");
// if removeCookie() function is not defined by TW core, define it here.
if (window.removeCookie===undefined) {
window.removeCookie=function(name) {
document.cookie = name+'=; expires=Thu, 01-Jan-1970 00:00:01 UTC; path=/;';
}
}
config.formatters.push( {
name: "nestedSliders",
match: "\\n?\\+{3}",
terminator: "\\s*\\={3}\\n?",
lookahead: "\\n?\\+{3}(\\+)?(\\([^\\)]*\\))?(\\!*)?(\\^(?:[^\\^\\*\\@\\[\\>]*\\^)?)?(\\*)?(\\@)?(?:\\{\\{([\\w]+[\\s\\w]*)\\{)?(\\[[^\\]]*\\])?(\\[[^\\]]*\\])?(?:\\}{3})?(\\#[^:]*\\:)?(\\>)?(\\.\\.\\.)?\\s*",
handler: function(w)
{
lookaheadRegExp = new RegExp(this.lookahead,"mg");
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source)
if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
{
var defopen=lookaheadMatch[1];
var cookiename=lookaheadMatch[2];
var header=lookaheadMatch[3];
var panelwidth=lookaheadMatch[4];
var transient=lookaheadMatch[5];
var hover=lookaheadMatch[6];
var buttonClass=lookaheadMatch[7];
var label=lookaheadMatch[8];
var openlabel=lookaheadMatch[9];
var panelID=lookaheadMatch[10];
var blockquote=lookaheadMatch[11];
var deferred=lookaheadMatch[12];
// location for rendering button and panel
var place=w.output;
// default to closed, no cookie, no accesskey, no alternate text/tip
var show="none"; var cookie=""; var key="";
var closedtext=">"; var closedtip="";
var openedtext="<"; var openedtip="";
// extra "+", default to open
if (defopen) show="block";
// cookie, use saved open/closed state
if (cookiename) {
cookie=cookiename.trim().slice(1,-1);
cookie="chkSlider"+cookie;
if (config.options[cookie]==undefined)
{ config.options[cookie] = (show=="block") }
show=config.options[cookie]?"block":"none";
}
// parse label/tooltip/accesskey: [label=X|tooltip]
if (label) {
var parts=label.trim().slice(1,-1).split("|");
closedtext=parts.shift();
if (closedtext.substr(closedtext.length-2,1)=="=")
{ key=closedtext.substr(closedtext.length-1,1); closedtext=closedtext.slice(0,-2); }
openedtext=closedtext;
if (parts.length) closedtip=openedtip=parts.join("|");
else { closedtip="show "+closedtext; openedtip="hide "+closedtext; }
}
// parse alternate label/tooltip: [label|tooltip]
if (openlabel) {
var parts=openlabel.trim().slice(1,-1).split("|");
openedtext=parts.shift();
if (parts.length) openedtip=parts.join("|");
else openedtip="hide "+openedtext;
}
var title=show=='block'?openedtext:closedtext;
var tooltip=show=='block'?openedtip:closedtip;
// create the button
if (header) { // use "Hn" header format instead of button/link
var lvl=(header.length>5)?5:header.length;
var btn = createTiddlyElement(createTiddlyElement(place,"h"+lvl,null,null,null),"a",null,buttonClass,title);
btn.onclick=onClickNestedSlider;
btn.setAttribute("href","javascript:;");
btn.setAttribute("title",tooltip);
}
else
var btn = createTiddlyButton(place,title,tooltip,onClickNestedSlider,buttonClass);
btn.innerHTML=title; // enables use of HTML entities in label
// set extra button attributes
btn.setAttribute("closedtext",closedtext);
btn.setAttribute("closedtip",closedtip);
btn.setAttribute("openedtext",openedtext);
btn.setAttribute("openedtip",openedtip);
btn.sliderCookie = cookie; // save the cookiename (if any) in the button object
btn.defOpen=defopen!=null; // save default open/closed state (boolean)
btn.keyparam=key; // save the access key letter ("" if none)
if (key.length) {
btn.setAttribute("accessKey",key); // init access key
btn.onfocus=function(){this.setAttribute("accessKey",this.keyparam);}; // **reclaim** access key on focus
}
btn.setAttribute("hover",hover?"true":"false");
btn.onmouseover=function(ev) {
// optional 'open on hover' handling
if (this.getAttribute("hover")=="true" && this.sliderPanel.style.display=='none') {
document.onclick.call(document,ev); // close transients
onClickNestedSlider(ev); // open this slider
}
// mouseover on button aligns floater position with button
if (window.adjustSliderPos) window.adjustSliderPos(this.parentNode,this,this.sliderPanel);
}
// create slider panel
var panelClass=panelwidth?"floatingPanel":"sliderPanel";
if (panelID) panelID=panelID.slice(1,-1); // trim off delimiters
var panel=createTiddlyElement(place,"div",panelID,panelClass,null);
panel.button = btn; // so the slider panel know which button it belongs to
btn.sliderPanel=panel; // so the button knows which slider panel it belongs to
panel.defaultPanelWidth=(panelwidth && panelwidth.length>2)?panelwidth.slice(1,-1):"";
panel.setAttribute("transient",transient=="*"?"true":"false");
panel.style.display = show;
panel.style.width=panel.defaultPanelWidth;
panel.onmouseover=function(event) // mouseover on panel aligns floater position with button
{ if (window.adjustSliderPos) window.adjustSliderPos(this.parentNode,this.button,this); }
// render slider (or defer until shown)
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
if ((show=="block")||!deferred) {
// render now if panel is supposed to be shown or NOT deferred rendering
w.subWikify(blockquote?createTiddlyElement(panel,"blockquote"):panel,this.terminator);
// align floater position with button
if (window.adjustSliderPos) window.adjustSliderPos(place,btn,panel);
}
else {
var src = w.source.substr(w.nextMatch);
var endpos=findMatchingDelimiter(src,"+++","===");
panel.setAttribute("raw",src.substr(0,endpos));
panel.setAttribute("blockquote",blockquote?"true":"false");
panel.setAttribute("rendered","false");
w.nextMatch += endpos+3;
if (w.source.substr(w.nextMatch,1)=="\n") w.nextMatch++;
}
}
}
}
)
function findMatchingDelimiter(src,starttext,endtext) {
var startpos = 0;
var endpos = src.indexOf(endtext);
// check for nested delimiters
while (src.substring(startpos,endpos-1).indexOf(starttext)!=-1) {
// count number of nested 'starts'
var startcount=0;
var temp = src.substring(startpos,endpos-1);
var pos=temp.indexOf(starttext);
while (pos!=-1) { startcount++; pos=temp.indexOf(starttext,pos+starttext.length); }
// set up to check for additional 'starts' after adjusting endpos
startpos=endpos+endtext.length;
// find endpos for corresponding number of matching 'ends'
while (startcount && endpos!=-1) {
endpos = src.indexOf(endtext,endpos+endtext.length);
startcount--;
}
}
return (endpos==-1)?src.length:endpos;
}
//}}}
//{{{
window.onClickNestedSlider=function(e)
{
if (!e) var e = window.event;
var theTarget = resolveTarget(e);
while (theTarget && theTarget.sliderPanel==undefined) theTarget=theTarget.parentNode;
if (!theTarget) return false;
var theSlider = theTarget.sliderPanel;
var isOpen = theSlider.style.display!="none";
// if SHIFT-CLICK, dock panel first (see [[MoveablePanelPlugin]])
if (e.shiftKey && config.macros.moveablePanel) config.macros.moveablePanel.dock(theSlider,e);
// toggle label
theTarget.innerHTML=isOpen?theTarget.getAttribute("closedText"):theTarget.getAttribute("openedText");
// toggle tooltip
theTarget.setAttribute("title",isOpen?theTarget.getAttribute("closedTip"):theTarget.getAttribute("openedTip"));
// deferred rendering (if needed)
if (theSlider.getAttribute("rendered")=="false") {
var place=theSlider;
if (theSlider.getAttribute("blockquote")=="true")
place=createTiddlyElement(place,"blockquote");
wikify(theSlider.getAttribute("raw"),place);
theSlider.setAttribute("rendered","true");
}
// show/hide the slider
if(config.options.chkAnimate && (!hasClass(theSlider,'floatingPanel') || config.options.chkFloatingSlidersAnimate))
anim.startAnimating(new Slider(theSlider,!isOpen,e.shiftKey || e.altKey,"none"));
else
theSlider.style.display = isOpen ? "none" : "block";
// reset to default width (might have been changed via plugin code)
theSlider.style.width=theSlider.defaultPanelWidth;
// align floater panel position with target button
if (!isOpen && window.adjustSliderPos) window.adjustSliderPos(theSlider.parentNode,theTarget,theSlider);
// if showing panel, set focus to first 'focus-able' element in panel
if (theSlider.style.display!="none") {
var ctrls=theSlider.getElementsByTagName("*");
for (var c=0; c<ctrls.length; c++) {
var t=ctrls[c].tagName.toLowerCase();
if ((t=="input" && ctrls[c].type!="hidden") || t=="textarea" || t=="select")
{ try{ ctrls[c].focus(); } catch(err){;} break; }
}
}
var cookie=theTarget.sliderCookie;
if (cookie && cookie.length) {
config.options[cookie]=!isOpen;
if (config.options[cookie]!=theTarget.defOpen) window.saveOptionCookie(cookie);
else window.removeCookie(cookie); // remove cookie if slider is in default display state
}
// prevent SHIFT-CLICK from being processed by browser (opens blank window... yuck!)
// prevent clicks *within* a slider button from being processed by browser
// but allow plain click to bubble up to page background (to close transients, if any)
if (e.shiftKey || theTarget!=resolveTarget(e))
{ e.cancelBubble=true; if (e.stopPropagation) e.stopPropagation(); }
Popup.remove(); // close open popup (if any)
return false;
}
//}}}
//{{{
// click in document background closes transient panels
document.nestedSliders_savedOnClick=document.onclick;
document.onclick=function(ev) { if (!ev) var ev=window.event; var target=resolveTarget(ev);
if (document.nestedSliders_savedOnClick)
var retval=document.nestedSliders_savedOnClick.apply(this,arguments);
// if click was inside a popup... leave transient panels alone
var p=target; while (p) if (hasClass(p,"popup")) break; else p=p.parentNode;
if (p) return retval;
// if click was inside transient panel (or something contained by a transient panel), leave it alone
var p=target; while (p) {
if ((hasClass(p,"floatingPanel")||hasClass(p,"sliderPanel"))&&p.getAttribute("transient")=="true") break;
p=p.parentNode;
}
if (p) return retval;
// otherwise, find and close all transient panels...
var all=document.all?document.all:document.getElementsByTagName("DIV");
for (var i=0; i<all.length; i++) {
// if it is not a transient panel, or the click was on the button that opened this panel, don't close it.
if (all[i].getAttribute("transient")!="true" || all[i].button==target) continue;
// otherwise, if the panel is currently visible, close it by clicking it's button
if (all[i].style.display!="none") window.onClickNestedSlider({target:all[i].button})
if (!hasClass(all[i],"floatingPanel")&&!hasClass(all[i],"sliderPanel")) all[i].style.display="none";
}
return retval;
};
//}}}
//{{{
// adjust floating panel position based on button position
if (window.adjustSliderPos==undefined) window.adjustSliderPos=function(place,btn,panel) {
if (hasClass(panel,"floatingPanel") && !hasClass(panel,"undocked")) {
// see [[MoveablePanelPlugin]] for use of 'undocked'
var rightEdge=document.body.offsetWidth-1;
var panelWidth=panel.offsetWidth;
var left=0;
var top=btn.offsetHeight;
if (place.style.position=="relative" && findPosX(btn)+panelWidth>rightEdge) {
left-=findPosX(btn)+panelWidth-rightEdge; // shift panel relative to button
if (findPosX(btn)+left<0) left=-findPosX(btn); // stay within left edge
}
if (place.style.position!="relative") {
var left=findPosX(btn);
var top=findPosY(btn)+btn.offsetHeight;
var p=place; while (p && !hasClass(p,'floatingPanel')) p=p.parentNode;
if (p) { left-=findPosX(p); top-=findPosY(p); }
if (left+panelWidth>rightEdge) left=rightEdge-panelWidth;
if (left<0) left=0;
}
panel.style.left=left+"px"; panel.style.top=top+"px";
}
}
//}}}
//{{{
// TW2.1 and earlier:
// hijack Slider stop handler so overflow is visible after animation has completed
Slider.prototype.coreStop = Slider.prototype.stop;
Slider.prototype.stop = function()
{ this.coreStop.apply(this,arguments); this.element.style.overflow = "visible"; }
// TW2.2+
// hijack Morpher stop handler so sliderPanel/floatingPanel overflow is visible after animation has completed
if (version.major+.1*version.minor+.01*version.revision>=2.2) {
Morpher.prototype.coreStop = Morpher.prototype.stop;
Morpher.prototype.stop = function() {
this.coreStop.apply(this,arguments);
var e=this.element;
if (hasClass(e,"sliderPanel")||hasClass(e,"floatingPanel")) {
// adjust panel overflow and position after animation
e.style.overflow = "visible";
if (window.adjustSliderPos) window.adjustSliderPos(e.parentNode,e.button,e);
}
};
}
//}}}
/***
|Version|0.7.0|
О плагине и разработке <<tiddler [[to do list template##main]] with:"regarding NestedSwitcherPlugin" with:orderNtpNotes noedit>>
***/
//{{{
// regexp helpers:
var begin = "^",
someDotsDontRemember = "\\.*",
someDots = "("+someDotsDontRemember+")",
mainHead = "%((?: tabs[_><]? )|(?: js )|(?:%+))%\\((\\w*)\\($",
mainHeadDontRemember = "%(?:(?: tabs[_><]? )|(?: js )|(?:%+))%\\(\\w*\\($",
terminator = "\\)\\)%%\\n?";
var nestedSwitchersFormatter =
{
name: "nestedSwitchers",
match: begin + someDotsDontRemember + mainHeadDontRemember,
matchAgain: begin + someDots + mainHead
}
nestedSwitchersFormatter.handler = function(w)
{
// check how many dots go before the %...% (the nesting level)
var matchAgainRegExp = new RegExp(this.matchAgain,"mg");
matchAgainRegExp.lastIndex = w.matchStart;
var dots = matchAgainRegExp.exec(w.source)[1];
var lookahead = begin + dots + mainHead +"((?:.|\n)*?)"+ begin + dots + terminator;
lookaheadRegExp = new RegExp(lookahead,"mg");
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
{
var param = lookaheadMatch[2];
switch(lookaheadMatch[1])
{
case " tabs< ":
case " tabs> ":
case " tabs_ ":
//# do extra stuff here, propagate
case " tabs ":
case "%":
if(param.substr(0,7) != "txtTabs")
param = "txtTabs" + param.substr(0,1).toUpperCase() +
param.substr(1);
this.tabsHandler(lookaheadMatch[3],w.output,dots,param,w);
break;
case " js ":
case "%%":
this.jsHandler(lookaheadMatch[3],w.output,dots,param,w);
break;
}
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
};
var tabNameAddition = "_text_:";
nestedSwitchersFormatter.tabsHandler = function(text,place,dots,cookieName,wikifier)
{
// create the place where tabs should be put
var el = "div", validTab = false,
wrapper = createTiddlyElement(null,el,null,"tabsetWrapper " + cookieName),
tabset = createTiddlyElement(wrapper,el,null,"tabset",null,{cookie:cookieName});
// parse the text
// .%% label 1 % tooltip 1 % or .<% label 1 %
// content 1
// ...
var match, startTextIndex, endTextIndex,
tabText, tabLabel, tabTooltip, transclusionArguments, tab, defaultTab,
dotsPattern = dots.replace(".","\\."),
labelTextPattern = "(?:[^%\\n]|(?:%%))*?", // any of ((not % or linebreak) or %%)
labelPattern = "%% +("+labelTextPattern+") +(?:% +("+labelTextPattern+") +)?"+
"(?:%(\\[\\["+labelTextPattern+"\\]\\]"+labelTextPattern+"))?%%$",
headPattern = begin + dotsPattern + labelPattern,
headRegExp = new RegExp(headPattern,"mg");
match = headRegExp.exec(text);
defaultTab = match ? match[1] : "";
while(match)
{
startTextIndex = match.index + match[0].length;
tabLabel = match[1] .replace("%%","%");
tabTooltip = (match[2] || "").replace("%%","%");
transclusionArguments = (match[3] || "").replace("%%","%");
if(match = headRegExp.exec(text))
endTextIndex = match.index;
else
endTextIndex = text.length;
tabText = transclusionArguments ? "<<tiddler "+transclusionArguments+">>" :
jQuery.trim(text.substring(startTextIndex,endTextIndex));
tab = createTiddlyElement(tabset,"a",null,"tab tabUnselected",null,{
tab : tabLabel, content: tabNameAddition + tabText,
href: "javascript:;"
})
if(tabTooltip)
tab.setAttribute("title",tabTooltip);
if(tabLabel)
// tab.appendChild(document.createTextNode(tabLabel));
wikify(tabLabel,tab,null,wikifier.tiddler)
tab.onclick = config.macros.tabs.onClickTab;
tab.title = tabTooltip;
createTiddlyElement(tab,"span",null,null," ",{style:"font-size:0pt;line-height:0px"});
//# is this necessary? this was taken from config.macros.tabs.handler
if(config.options[cookieName] == tabLabel)
validTab = true;
}
if(!validTab)
config.options[cookieName] = defaultTab;
place.appendChild(wrapper);
config.macros.tabs.switchTab(tabset,config.options[cookieName]);
};
// hijack config.macros.tabs.switchTab so that is takes into account "text" params
// (aside normal "where text is" params)
config.macros.tabs.bare_switchTab = config.macros.tabs.switchTab;
config.macros.tabs.switchTab = function(tabset,tab)
{
if(!store.ntp_orig_getTiddlerText)
{
store.ntp_orig_getTiddlerText = store.getTiddlerText;
store.getTiddlerText = function(title,defaultText)
{
if(title.substr(0,tabNameAddition.length) == tabNameAddition)
return title.substr(tabNameAddition.length);
return this.ntp_orig_getTiddlerText(title,defaultText);
};
store.ntp_orig_getTiddler = store.getTiddler;
store.getTiddler = function(title)
{
if(title.substr(0,tabNameAddition.length) == tabNameAddition) {
var tidElem = story.findContainingTiddler(tabset),
tidName = tidElem ? tidElem.getAttribute("tiddler") : "";
return store.fetchTiddler(tidName);
}
return this.ntp_orig_getTiddler(title);
};
}
this.bare_switchTab.apply(this,arguments);
if(store.ntp_orig_getTiddlerText)
{
store.getTiddlerText = store.ntp_orig_getTiddlerText;
store.getTiddler = store.ntp_orig_getTiddler;
}
store.ntp_orig_getTiddlerText = null;
// this complicated stuff about .getTiddlerText is needed for nested tabs
// the stuff about .getTiddler gives the right "tiddler" context for the wikification
};
nestedSwitchersFormatter.jsHandler = function(text,place,dots,param,wikifier)
{
//# for testing:
createTiddlyElement(place,"div",null,null,"js: "+text,{style: "color:green; white-space:pre;"})
};
config.formatters.push(nestedSwitchersFormatter);
//}}}
/***
|Name|RearrangeTiddlersPlugin|
|Source|{{DDn{old:}}} http://www.TiddlyTools.com/#RearrangeTiddlersPlugin|
|Version|2.0.2|
|Author|Yakov Litvin|
|Contributor|Eric Shulman|
|OriginalAuthor|Joe Raii|
|License|{{DDn{old:}}} http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.4.0|
|Type|plugin|
|Description|drag tiddlers by title to re-order story column display|
***/
//{{{
if (Story.prototype.rearrangeTiddlersHijack_refreshTiddler === undefined) {
Story.prototype.rearrangeTiddlersHijack_refreshTiddler = Story.prototype.refreshTiddler;
Story.prototype.refreshTiddler = function(title,template)
{
this.rearrangeTiddlersHijack_refreshTiddler.apply(this,arguments);
var theTiddler = document.getElementById(this.tiddlerId(title));
if(!theTiddler) return;
// find title element
var theHandle = jQuery(theTiddler).children(".title")[0];
if(!theHandle) return theTiddler;
Drag.init(theHandle, theTiddler, 0, 0, null, null);
theHandle.style.cursor = "move";
theHandle.title = "drag title to re-arrange tiddlers, click for more options...";
theTiddler.onDrag = function(x,y,myElem)
{
if (this.style.position != "relative") {
this.savedstyle = this.style.position;
this.style.position = "relative";
}
y = myElem.offsetTop;
var next = myElem.nextSibling;
var prev = myElem.previousSibling;
if (next && y + myElem.offsetHeight > next.offsetTop + next.offsetHeight/2) {
myElem.parentNode.removeChild(myElem);
next.parentNode.insertBefore(myElem, next.nextSibling);//elems[pos+1]);
myElem.style["top"] = -next.offsetHeight/2+"px";
}
if (prev && y < prev.offsetTop + prev.offsetHeight/2) {
myElem.parentNode.removeChild(myElem);
prev.parentNode.insertBefore(myElem, prev);
myElem.style["top"] = prev.offsetHeight/2+"px";
}
};
theTiddler.onDragEnd = function(x,y,myElem)
{
myElem.style["top"] = "0px";
if (this.savedstyle != undefined)
this.style.position = this.savedstyle;
};
theHandle.onclick = function(ev)
{
ev = ev || window.event;
var p = Popup.create(this); if (!p) return;
var b = createTiddlyButton(createTiddlyElement(p,"li"),
"\u25B2 move to top of column ","move this tiddler to the top of the story column",
function() {
var t = story.getTiddler(this.getAttribute("tid"));
t.parentNode.insertBefore(t,t.parentNode.firstChild); // move to top of column
window.scrollTo(0,ensureVisible(t));
return false;
});
b.setAttribute("tid",title);
var b = createTiddlyButton(createTiddlyElement(p,"li"),
"\u25BC move to bottom of column ","move this tiddler to the bottom of the story column",
function() {
var t = story.getTiddler(this.getAttribute("tid"));
t.parentNode.insertBefore(t,null); // move to bottom of column
window.scrollTo(0,ensureVisible(t));
return false;
});
b.setAttribute("tid",title);
Popup.show();
ev.cancelBubble = true;
if (ev.stopPropagation) ev.stopPropagation();
return false;
};
return theTiddler;
}
}
/**************************************************
* dom-drag.js
* 09.25.2001
* www.youngpup.net
**************************************************
* 10.28.2001 - fixed minor bug where events
* sometimes fired off the handle, not the root.
**************************************************/
var Drag = {
obj: null,
init: function(o, oRoot, minX, maxX, minY, maxY)
{
o.onmousedown = Drag.start;
o.root = oRoot && oRoot != null ? oRoot : o ;
if (isNaN(parseInt(o.root.style.left))) o.root.style.left = "0px";
if (isNaN(parseInt(o.root.style.top ))) o.root.style.top = "0px";
o.minX = typeof minX != 'undefined' ? minX : null;
o.minY = typeof minY != 'undefined' ? minY : null;
o.maxX = typeof maxX != 'undefined' ? maxX : null;
o.maxY = typeof maxY != 'undefined' ? maxY : null;
o.root.onDragStart = new Function();
o.root.onDragEnd = new Function();
o.root.onDrag = new Function();
},
start: function(e)
{
var o = Drag.obj = this;
e = Drag.fixE(e);
var y = parseInt(o.root.style.top);
var x = parseInt(o.root.style.left);
o.root.onDragStart(x, y, Drag.obj.root);
o.lastMouseX = e.clientX;
o.lastMouseY = e.clientY;
if (o.minX != null) o.minMouseX = e.clientX - x + o.minX;
if (o.maxX != null) o.maxMouseX = o.minMouseX + o.maxX - o.minX;
if (o.minY != null) o.minMouseY = e.clientY - y + o.minY;
if (o.maxY != null) o.maxMouseY = o.minMouseY + o.maxY - o.minY;
document.onmousemove = Drag.drag;
document.onmouseup = Drag.end;
Drag.obj.root.style["z-index"] = "10";
return false;
},
drag: function(e)
{
e = Drag.fixE(e);
var o = Drag.obj;
var ey = e.clientY;
var ex = e.clientX;
var y = parseInt(o.root.style.top);
var x = parseInt(o.root.style.left);
var nx, ny;
if (o.minX != null) ex = Math.max(ex, o.minMouseX);
if (o.maxX != null) ex = Math.min(ex, o.maxMouseX);
if (o.minY != null) ey = Math.max(ey, o.minMouseY);
if (o.maxY != null) ey = Math.min(ey, o.maxMouseY);
nx = x + (ex - o.lastMouseX);
ny = y + (ey - o.lastMouseY);
Drag.obj.root.style["left"] = nx + "px";
Drag.obj.root.style["top"] = ny + "px";
Drag.obj.lastMouseX = ex;
Drag.obj.lastMouseY = ey;
Drag.obj.root.onDrag(nx, ny, Drag.obj.root);
return false;
},
end: function()
{
document.onmousemove = null;
document.onmouseup = null;
Drag.obj.root.style["z-index"] = "0";
Drag.obj.root.onDragEnd(parseInt(Drag.obj.root.style["left"]), parseInt(Drag.obj.root.style["top"]), Drag.obj.root);
Drag.obj = null;
},
fixE: function(e)
{
if (typeof e == 'undefined') e = window.event;
if (typeof e.layerX == 'undefined') e.layerX = e.offsetX;
if (typeof e.layerY == 'undefined') e.layerY = e.offsetY;
return e;
}
};
//}}}
/***
***/
//{{{
// tagging macro
config.macros.tagging.label = "записи с этим тэгом:";
config.macros.tagging.labelNotTag = "нет записей с этим тэгом";
config.macros.tagging.tooltip = "Список записей с тэгом '%0'";
// tags macro
config.views.wikified.tag.labelNoTags = "нет тэгов";
config.views.wikified.tag.labelTags = "тэги:";
config.views.wikified.tag.openTag = "Открыть тэг '%0'";
config.views.wikified.tag.tooltip = "Показать записи с тэгом '%0'";
config.views.wikified.tag.openAllText = "Открыть все";
config.views.wikified.tag.openAllTooltip = "Открыть все эти записи";
config.views.wikified.tag.popupNone = "Других записей с тэгом '%0' нет";
// closeAll macro
config.macros.closeAll.label = "закрыть все";
config.macros.closeAll.prompt = readOnly ? "закрыть все открытые записи" : "закрыть все открытые записи (кроме редактируемых)";
// search macro
config.macros.search.label = "искать";
config.macros.search.prompt = "Искать на этом сайте (в этой TiddlyWiki)";
// toolbar commands (some)
config.commands.closeTiddler.text = "закрыть";
config.commands.closeTiddler.tooltip = "закрыть эту запись";
// simple translation of view->date (uses numerical representation of months)
//# to do: convert to "13 июля 2016"
config.macros.view.views.date = function(value,place,params,wikifier,paramString,tiddler) {
value = Date.convertFromYYYYMMDDHHMM(value);
dateString = params[2] ? value.formatString(params[2]) : value.toLocaleDateString(); // "13.07.2016"
createTiddlyText(place,dateString);
}
// "created" above tiddler's title
config.views.wikified.createdPrompt = "создано";
//}}}
/***
|Name|SaveAsPlugin|
|Source||
|Documentation||
|Version|2.10.1|
|Author|Eric Shulman|
|Tweaked by|Yakov Litvin (after v2.7.1)|
|License||
|~CoreVersion|2.1|
|Type|plugin|
|Description|Save current document to another path/filename|
***/
//{{{
version.extensions.SaveAsPlugin = { major: 2, minor: 10, revision: 1, date: new Date(2016,5,6) };
/* YL tweak+ */ // code adapted from SharedTiddlersPlugin to handle relative paths
var getPathFromURL = function(url) {
return (url.lastIndexOf("/") > -1) ?
url.substr(0, url.lastIndexOf("/") + 1) : "";
};
// limitedly turns URI (URL) reference into an absolute URI (URL) and windows paths into URL
var resolveURL = function(url) {
if (url.search(/^((http(s)?)|(file)):/) != 0) {
// no protocol prefix..
if (url.search(/^(.\:\\)|(\\\\)|(\/)/) != 0) // is relative?
// as Unix filesystem root is "/", urls starting with it are not considered as relative
url = getPathFromURL(document.location.toString()) + url;
else
// "url" is an "absolute" path to a local file. Prefix it with file://
url = "file://" + url;
// replace every \ by a /, to cover Windows style pathes
url = url.replace(/\\/mg,"/");
}
return url;
};
/* YL tweak= */
config.macros.saveAs = {
label: 'save as...',
labelparam: 'label:',
prompt: 'Save current document to a different path/file',
promptparam: 'prompt:',
filePrompt: 'Please select or enter a target path/filename',
targetparam: 'target:',
defaultFilename: 'new.html',
filenameparam: 'filename:',
currfilekeyword: 'here',
typeparam: 'type:',
type_TW: 'tw', type_PS: 'ps', type_TX: 'tx', type_CS: 'cs', type_NF: 'nf', // file type tokens
type_map: {
tiddlywiki:'tw', tw:'tw', wiki: 'tw',
purestore: 'ps', ps:'ps', store:'ps',
plaintext: 'tx', tx:'tx', text: 'tx',
comma: 'cs', cs:'cs', csv: 'cs',
newsfeed: 'nf', nf:'nf', xml: 'nf', rss:'nf'
},
/* YL tweak */ filterExpression: 'filter:',
limitparam: 'limit:',
/* YL tweak */ scriptparam: 'process:',
replaceparam: 'replace',
mergeparam: 'merge',
quietparam: 'quiet',
openparam: 'open',
askParam: 'ask',
hereParam: 'here',
askMsg: "Enter a tag filter (use * for all tiddlers, 'none' for blank document)",
hereMsg: 'Enter a tiddler title',
emptyParam: 'none',
confirmmsg: "Found %0 tiddlers matching\n\n'%1'\n\nPress OK to proceed",
mergeprompt: '%0\nalready contains tiddler definitions.\n'
+'\nPress OK to add new/revised tiddlers to current file contents.'
+'\nPress Cancel to completely replace file contents',
mergestatus: 'Merged %0 new/revised tiddlers and %1 existing tiddlers',
okmsg: '%0 tiddlers written to %1',
failmsg: 'An error occurred while creating %1',
filter: '',
handler: function(place,macroName,params) {
if ((params[0]||'').startsWith(this.labelparam))
var label=params.shift().substr(this.labelparam.length);
if ((params[0]||'').startsWith(this.promptparam))
var prompt=params.shift().substr(this.promptparam.length);
if ((params[0]||'').startsWith(this.targetparam))
var target=params.shift().substr(this.targetparam.length);
if ((params[0]||'').startsWith(this.filenameparam))
var filename=params.shift().substr(this.filenameparam.length);
if ((params[0]||'').startsWith(this.typeparam))
var filetype=this.type_map[params.shift().substr(this.typeparam.length).toLowerCase()];
/* YL tweak */ if ((params[0]||'').startsWith(this.filterExpression))
var filterExpression=params.shift().substr(this.filterExpression.length);
if ((params[0]||'').startsWith(this.limitparam))
var limit=params.shift().substr(this.limitparam.length);
/* YL tweak */ if ((params[0]||'').startsWith(this.scriptparam))
var script=params.shift().substr(this.scriptparam.length);
var q=((params[0]||'')==this.quietparam); if (q) params.shift();
var o=((params[0]||'')==this.replaceparam); if (o) params.shift();
var m=((params[0]||'')==this.mergeparam); if (m) params.shift();
var a=((params[0]||'')==this.openparam); if (a) params.shift();
var btn=createTiddlyButton(place,label||this.label,prompt||this.prompt,
function(){ config.macros.saveAs.go( this.getAttribute('target'),
this.getAttribute('filename'), this.getAttribute('filetype'),
this.getAttribute('filter'),
/* YL tweak */ this.getAttribute('filterExpression'),
this.getAttribute('limit'),
/* YL tweak */ this.getAttribute('modScript'),
this.getAttribute('quiet')=='true',
this.getAttribute('overwrite')=='true',
this.getAttribute('merge')=='true',
this.getAttribute('autoopen')=='true',
this);
return false;
});
if (target) btn.setAttribute('target',target);
if (filename) btn.setAttribute('filename',filename);
btn.setAttribute('filetype',filetype||this.type_TW);
/* YL tweak */ btn.setAttribute('filterExpression',filterExpression||'');
btn.setAttribute('filter',params.join(' '));
/* YL tweak */ btn.setAttribute('modScript',script||'');
btn.setAttribute('limit',limit||0);
btn.setAttribute('quiet',q?'true':'false');
btn.setAttribute('overwrite',o?'true':'false');
btn.setAttribute('merge',m?'true':'false');
btn.setAttribute('autoopen',a?'true':'false');
},
go: function(target,filename,filetype,filter,filterExpr,limit,script,quiet,overwrite,merge,autoopen,here){
/* YL tweak */ // added the "filterExpr" and "script" params
var cm=config.messages; // abbreviation
var cms=config.macros.saveAs; // abbreviation
if (window.location.protocol!='file:') // make sure we are local
{ displayMessage(cm.notFileUrlError); return; }
// get tidders, confirm filtered results
/* YL tweak+ */ filter = filter.length ? "[tag["+filter+"]]" : null;
/* YL tweak= */ filterExpr = filterExpr.length ? filterExpr + (filter ? " "+filter : "") : (filter ? filter : "");
var tids=cms.selectTiddlers(filterExpr,here);
if (tids===false) return; // cancelled by user
if (cms.filter!=cms.emptyParam && cms.filter.length && !quiet)
if (!confirm(cms.confirmmsg.format([tids.length,cms.filter]))) return;
/* YL tweak+ */ // switch tiddlers from store to their copies, launch the script
for(var i = 0; i < tids.length; i++) {
tids[i] = jQuery.extend(true, new Tiddler(), tids[i]);
// keep the original titles for correct markup blocks updating and other things
tids[i].orig_title = tids[i].title;
}
// wrap the script so that the "tids" var can be used even if the code is minified
script = "config.macros.saveAs.f = function(tids) {" + script + "};"
eval(script);
/* YL tweak= */ this.f(tids);
// get target path/filename
if (!filetype) filetype=this.type_TW;
target=target||cms.getTarget(filename,filetype==this.type_TX?'txt':filetype==this.type_CS?'csv':'html');
if (!target) return; // cancelled by user
/* YL tweak+ */ var link = resolveURL(target);
/* YL tweak= */ target = getLocalPath(link);
var samefile=link==decodeURIComponent(window.location.href);
var p=getLocalPath(document.location.href);
if (samefile) {
if (config.options.chkSaveBackups)
{ var t=loadOriginal(p);if(t)saveBackup(p,t); }
if (config.options.chkGenerateAnRssFeed && saveRss instanceof Function)
saveRss(p);
}
var notes='';
var total={val:0};
var out=this.assembleFile(target,filetype,tids,limit||0,notes,quiet,overwrite,merge,total);
var ok=saveFile(target,out);
if (ok && autoopen) {
if (!samefile) window.open(link).focus();
else { store.setDirty(false); window.location.reload(); }
}
if (!quiet || !(ok && autoopen))
displayMessage((ok?this.okmsg:this.failmsg).format([total.val,target]),link);
},
selectTiddlers: function(filter,here) {
var cms=config.macros.saveAs; // abbreviation
var tids=[]; cms.filter=filter||'';
if (filter==cms.emptyParam)
return tids;
if (filter==config.macros.saveAs.hereParam) {
var here=story.findContainingTiddler(here);
if (here) var tid=here.getAttribute('tiddler');
else var tid=prompt(config.macros.saveAs.hereMsg,'');
while (tid && !store.tiddlerExists(tid)) {
var err='"'+tid+'" not found.\nPlease try again.\n\n';
var tid=prompt(err+config.macros.saveAs.hereMsg,tid);
}
if (!tid) return false; // cancelled by user
return [store.getTiddler(tid)];
}
if (filter==config.macros.saveAs.askParam) {
filter=prompt(config.macros.saveAs.askMsg,'');
if (!filter) return false; // cancelled by user
cms.filter=filter=='*'?'':filter;
}
if (!filter||!filter.length||filter=='*') tids=store.getTiddlers('title');
/* YL tweak */ else tids=store.filterTiddlers(filter);
return tids;
},
getTarget: function(defName,defExt) {
var cms=config.macros.saveAs; // abbreviation
// get new target path/filename
var newPath=getLocalPath(window.location.href);
var slashpos=newPath.lastIndexOf('/'); if (slashpos==-1) slashpos=newPath.lastIndexOf('\\');
if (slashpos!=-1) newPath=newPath.substr(0,slashpos+1); // trim filename
if (!defName||!defName.length) { // use current filename as default
var p=getLocalPath(window.location.href);
var s=p.lastIndexOf('/'); if (s==-1) s=p.lastIndexOf('\\');
if (s!=-1) defName=p.substr(s+1);
}
var defFilename=(defName||cms.defaultFilename).replace(/.html$/,'.'+defExt);
var target=cms.askForFilename(cms.filePrompt,newPath,defFilename,defExt);
if (!target) return; // cancelled by user
// if specified file does not include a path, assemble fully qualified path and filename
var slashpos=target.lastIndexOf('/'); if (slashpos==-1) slashpos=target.lastIndexOf('\\');
if (slashpos==-1) target=target+(defName||cms.defaultFilename).replace(/.html$/,'.'+defExt);
return target;
},
askForFilename: function(msg,path,file,defExt) {
/* if(window.Components) { // moz
try {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var nsIFilePicker = window.Components.interfaces.nsIFilePicker;
var picker = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker);
picker.init(window, msg, nsIFilePicker.modeSave);
var thispath = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
thispath.initWithPath(path);
picker.displayDirectory=thispath;
picker.defaultExtension=defExt||'html';
picker.defaultString=file;
picker.appendFilters(nsIFilePicker.filterAll|nsIFilePicker.filterText|nsIFilePicker.filterHTML);
if (picker.show()!=nsIFilePicker.returnCancel) var result=picker.file.path;
}
catch(e) { alert('error during local file access: '+e.toString()) }
} else {
*/ try { // IE, XP/Vista only
var s = new ActiveXObject('UserAccounts.CommonDialog');
s.Filter='All files|*.*|Text files|*.txt|HTML files|*.htm;*.html|';
s.FilterIndex=(defExt=='txt')?2:3; // default to HTML files;
s.InitialDir=path;
s.FileName=file;
if (s.showOpen()) var result=s.FileName;
}
catch(e) { var result=prompt(msg,path+file); } // fallback for other browsers
// }
return result;
},
plainTextHeader:
'Source:\n\t%0\n'
+'Title:\n\t%1\n'
+'Subtitle:\n\t%2\n'
+'Created:\n\t%3 by %4\n'
+'Application:\n\tTiddlyWiki %5 / %6 %7\n\n',
plainTextTiddler:
'- - - - - - - - - - - - - - -\n'
+'| title: %0\n'
+'| created: %1\n'
+'| modified: %2\n'
+'| edited by: %3\n'
+'| tags: %4\n'
+'- - - - - - - - - - - - - - -\n'
+'%5\n',
plainTextFooter:
'',
newsFeedHeader:
'<'+'?xml version="1.0"?'+'>\n'
+'<rss version="2.0">\n'
+'<channel>\n'
+'<title>%1</title>\n'
+'<link>%0</link>\n'
+'<description>%2</description>\n'
+'<language>en-us</language>\n'
+'<copyright>Copyright '+(new Date().getFullYear())+' %4</copyright>\n'
+'<pubDate>%3</pubDate>\n'
+'<lastBuildDate>%3</lastBuildDate>\n'
+'<docs>http://blogs.law.harvard.edu/tech/rss</docs>\n'
+'<generator>TiddlyWiki %5 / %6 %7</generator>\n',
newsFeedTiddler:
'\n%0\n',
newsFeedFooter:
'</channel></rss>',
pureStoreHeader:
'<html><body>'
+'<style type="text/css">'
+' #storeArea {display:block;margin:1em;}'
+' #storeArea div {padding:0.5em;margin:1em;border:2px solid black;height:10em;overflow:auto;}'
+' #pureStoreHeading {width:100%;text-align:left;background-color:#eeeeee;padding:1em;}'
+'</style>'
+'<div id="pureStoreHeading">'
+' TiddlyWiki "PureStore" export file<br>'
+' Source'+': <b>%0</b><br>'
+' Title: <b>%1</b><br>'
+' Subtitle: <b>%2</b><br>'
+' Created: <b>%3</b> by <b>%4</b><br>'
+' TiddlyWiki %5 / %6 %7<br>'
+' Notes:<hr><pre>%8</pre>'
+'</div>'
+'<div id="storeArea">',
pureStoreTiddler:
'%0\n%1',
pureStoreFooter:
'</div><!--POST-BODY-START-->\n<!--POST-BODY-END--></body></html>',
assembleFile: function(target,filetype,tids,limit,notes,quiet,overwrite,merge,total) {
var revised='';
var now = new Date().toLocaleString();
var src=convertUnicodeToUTF8(document.location.href);
var title = convertUnicodeToUTF8(wikifyPlain('SiteTitle').htmlEncode());
var subtitle = convertUnicodeToUTF8(wikifyPlain('SiteSubtitle').htmlEncode());
var user = convertUnicodeToUTF8(config.options.txtUserName.htmlEncode());
var twver = version.major+'.'+version.minor+'.'+version.revision;
var v=version.extensions.SaveAsPlugin; var pver = v.major+'.'+v.minor+'.'+v.revision;
var headerargs=[src,title,subtitle,now,user,twver,'SaveAsPlugin',pver,notes];
switch (filetype) {
case this.type_TX: // plain text
var header=this.plainTextHeader.format(headerargs);
var footer=this.plainTextFooter;
break;
case this.type_CS: // comma-separated
var fields={};
for (var i=0; i<tids.length; i++) for (var f in tids[i].fields) fields[f]=f;
var names=['title','created','modified','modifier','tags','text'];
for (var f in fields) names.push(f);
var header=names.join(',')+'\n';
var footer='';
break;
case this.type_NF: // news feed (XML)
headerargs[0]=store.getTiddlerText('SiteUrl','');
var header=this.newsFeedHeader.format(headerargs);
var footer=this.newsFeedFooter;
tids=store.sortTiddlers(tids,'-modified');
break;
case this.type_PS: // PureStore (no code)
var header=this.pureStoreHeader.format(headerargs);
var footer=this.pureStoreFooter;
break;
case this.type_TW: // full TiddlyWiki
default:
var currPath=getLocalPath(window.location.href);
var original=loadFile(currPath);
if (!original) { alert(config.messages.cantSaveError); return; }
var posDiv = locateStoreArea(original);
if (!posDiv) { alert(config.messages.invalidFileError.format([currPath])); return; }
var header = original.substr(0,posDiv[0]+startSaveArea.length)+'\n';
var footer = '\n'+original.substr(posDiv[1]);
break;
}
if (parseInt(limit)!=0) tids=tids.slice(0,limit);
var out=this.getData(target,filetype,tids,quiet,overwrite,merge,fields);
var revised = header+convertUnicodeToUTF8(out.join('\n'))+footer;
// if full TW, insert page title and language attr, and reset MARKUP blocks as needed...
/* YL tweak+ */ // for that, temporary change the store so that already processed tiddlers are used
var createContextStore = function(tids) {
var contextStore = new TiddlyWiki();
// copy all the tiddlers:
store.forEachTiddler(function(title,tiddler){ contextStore.addTiddler(tiddler); });
// substitute original tiddlers with their processed "images":
for(var i = 0; i < tids.length; i++) {
contextStore.deleteTiddler(tids[i].orig_title);
contextStore.addTiddler(tids[i]);
}
return contextStore;
/* YL tweak= */ }
if (filetype==this.type_TW) {
/* YL tweak+ */ // change the store so that in this context already processed tiddlers are used:
var mainStore = store;
/* YL tweak= */ store = createContextStore(tids);
var newSiteTitle=convertUnicodeToUTF8(getPageTitle()).htmlEncode();
revised=revised.replaceChunk('<title'+'>','</title'+'>',' ' + newSiteTitle + ' ');
revised=updateLanguageAttribute(revised);
var titles=[]; for (var i=0; i<tids.length; i++) titles.push(tids[i].title);
revised=updateMarkupBlock(revised,'PRE-HEAD',
titles.contains('MarkupPreHead')? 'MarkupPreHead' :null);
revised=updateMarkupBlock(revised,'POST-HEAD',
titles.contains('MarkupPostHead')?'MarkupPostHead':null);
revised=updateMarkupBlock(revised,'PRE-BODY',
titles.contains('MarkupPreBody')? 'MarkupPreBody' :null);
revised=updateMarkupBlock(revised,'POST-SCRIPT',
titles.contains('MarkupPostBody')?'MarkupPostBody':null);
/* YL tweak */ // return the original store:
/* YL tweak */ store = mainStore;
}
total.val=out.length;
return revised;
},
getData: function(target,filetype,tids,quiet,overwrite,merge,fields) {
// output selected tiddlers and gather list of titles (for use with merge)
var out=[]; var titles=[];
var url=store.getTiddlerText('SiteUrl','');
for (var i=0; i<tids.length; i++) {
out.push(this.formatItem(store,filetype,tids[i],url,fields));
titles.push(tids[i].title);
}
// if TW or PureStore format, ask to merge with existing tiddlers (if any)
if (filetype==this.type_TW || filetype==this.type_PS) {
if (overwrite) return out; // skip merge... forced overwrite
var txt=loadFile(target);
if (txt && txt.length) {
var remoteStore=new TiddlyWiki();
if (version.major+version.minor*.1+version.revision*.01<2.52) txt=convertUTF8ToUnicode(txt);
if (remoteStore.importTiddlyWiki(txt) && (merge||confirm(this.mergeprompt.format([target])))) {
var existing=remoteStore.getTiddlers('title');
for (var i=0; i<existing.length; i++)
if (!titles.contains(existing[i].title))
out.push(this.formatItem(remoteStore,filetype,existing[i],url));
if (!quiet) displayMessage(this.mergestatus.format([tids.length,out.length-tids.length]));
}
}
}
return out;
},
formatItem: function(s,f,t,u,fields) {
if (f==this.type_TW)
var r=s.getSaver().externalizeTiddler(s,t);
if (f==this.type_PS)
var r=this.pureStoreTiddler.format([t.title,s.getSaver().externalizeTiddler(s,t)]);
if (f==this.type_NF)
var r=this.newsFeedTiddler.format([t.saveToRss(u)]);
if (f==this.type_TX)
var r=this.plainTextTiddler.format([t.title, t.created.toLocaleString(), t.modified.toLocaleString(),
t.modifier, String.encodeTiddlyLinkList(t.tags), t.text]);
if (f==this.type_CS) {
function toCSV(t) { return '"'+t.replace(/"/g,'""')+'"'; } // always encode CSV
var out=[ toCSV(t.title), toCSV(t.created.toLocaleString()), toCSV(t.modified.toLocaleString()),
toCSV(t.modifier), toCSV(String.encodeTiddlyLinkList(t.tags)), toCSV(t.text) ];
for (var f in fields) out.push(toCSV(t.fields[f]||''));
var r=out.join(',');
}
return r||'';
}
};
//}}}
//{{{
// automatically add saveAs to backstage
config.tasks.saveAs = {
text: 'saveAs',
tooltip: config.macros.saveAs.prompt,
action: function(){ clearMessage(); config.macros.saveAs.go(); }
}
config.backstageTasks.splice(config.backstageTasks.indexOf('save')+1,0,'saveAs');
//}}}
/***
|''Version''|0.1.1|
|''Author''|Yakov Litvin|
|''Description''|This plugin fixes [[issue 146|https://github.com/TiddlyWiki/tiddlywiki/issues/146]] and ... [{{{TiddlyWiki.prototype.saveTiddler}}}]|
|''Overwriting notice''|This plugin overwrites the {{{Story.prototype.saveTiddler}}} and {{{TiddlyWiki.prototype.saveTiddler}}} methods. So any plugin that hijack any of these should use the {{{Requires}}} slice mentioning this plugin (if it is installed); plugins that overwrite these functions will likely cause conflicts/won't work.|
***/
//{{{
Story.prototype.saveTiddler = function(title,minorUpdate)
{
var tiddlerElem = this.getTiddler(title);
if(!tiddlerElem)
return null;
var fields = {};
this.gatherSaveFields(tiddlerElem,fields);
var newTitle = fields.title || title;
if(!store.tiddlerExists(newTitle)) {
newTitle = newTitle.trim();
var creator = config.options.txtUserName;
}
if(store.tiddlerExists(newTitle) && newTitle != title) {
if(!confirm(config.messages.overwriteWarning.format([newTitle.toString()])))
return null;
// was: title = newTitle;
// title = newTitle; // this line causes the bug
// this line is to be deleted; learn when and why this was introduced
// the next 4 usages of "title" marked with "+>" should work fine without this
}
/*+>*/ if(newTitle != title)
this.closeTiddler(newTitle,false);
tiddlerElem.id = this.tiddlerId(newTitle);
tiddlerElem.setAttribute("tiddler",newTitle);
tiddlerElem.setAttribute("template",DEFAULT_VIEW_TEMPLATE);
tiddlerElem.setAttribute("dirty","false");
if(config.options.chkForceMinorUpdate)
minorUpdate = !minorUpdate;
if(!store.tiddlerExists(newTitle))
minorUpdate = false;
var newDate = new Date();
/*+>*/ if(store.tiddlerExists(title)) {
/*+>*/ var t = store.fetchTiddler(title);
var extendedFields = t.fields;
creator = t.creator;
} else
extendedFields = merge({},config.defaultCustomFields);
for(var n in fields)
if(!TiddlyWiki.isStandardField(n))
extendedFields[n] = fields[n];
/*+>*/ var tiddler = store.saveTiddler(title,newTitle,fields.text,minorUpdate ? undefined : config.options.txtUserName,minorUpdate ? undefined : newDate,fields.tags,extendedFields,null,null,creator);
autoSaveChanges(null,[tiddler]);
return newTitle;
};
//}}}
// // Ещё один, более мелкий фикс (делает более ясным код {{{TiddlyWiki.prototype.saveTiddler}}}, исправляет назначение custom fields в случае, когда ...); важен для renaming в SMP!
//{{{
TiddlyWiki.prototype.saveTiddler = function(title,newTitle,newBody,modifier,modified,tags,fields,clearChangeCount,created,creator)
{
var tiddler;
if(title instanceof Tiddler) {
tiddler = title;
title = tiddler.title;
} else
tiddler = this.fetchTiddler(title);
if(tiddler) {
created = created || tiddler.created; // Preserve created date
creator = creator || tiddler.creator;
this.deleteTiddler(title);
} else {
created = created || modified;
tiddler = new Tiddler();
}
fields = merge(merge(tiddler ? tiddler.fields : {},fields), config.defaultCustomFields,true);
tiddler.set(newTitle,newBody,modifier,modified,tags,created,fields,creator);
this.addTiddler(tiddler);
if(clearChangeCount)
tiddler.clearChangeCount();
else
tiddler.incChangeCount();
if(title != newTitle)
this.notify(title,true);
this.notify(newTitle,true);
if(window.location.protocol == "file:")
this.setDirty(true);
return tiddler;
};
//}}}
/***
|''Version''|1.6.5|
***/
//{{{
// styling helpers
var getStylesFromSection = function(sectionName) {
var css = store.getTiddlerText("SetCommonStylesPlugin##"+sectionName,"");
return css.replace("{{{","/"+"*{{{*"+"/").replace("}}}","/"+"*}}}*"+"/");
};
var setCssShadow = function(sectionName,shadowName) {
config.shadowTiddlers[shadowName] = getStylesFromSection(sectionName);
store.addNotification(shadowName, refreshStyles);
store.addNotification("ColorPalette",function(smth,doc) {
refreshStyles(shadowName,doc);
});
};
// set styles
setCssShadow("Semantics", "CommonSemanticsStyleSheet");
setCssShadow("Representation", "CommonRepresenationStyleSheet");
setCssShadow("Representation tools", "CommonRepresToolsStyleSheet");
//if(jQuery.browser.mozilla) // now done via CSS
setCssShadow("FireFox CSS", "FireFoxFixesStyleSheet");
csp_old_wikify = wikify;
wikify = function(source,output,highlightRegExp,tiddler) {
csp_old_wikify.apply(this,arguments);
jQuery(".NTerm").each(function(){
// avoid multiple wrapping because of ~ wikifications:
if(!jQuery(this.parentNode).hasClass("NTermWrap"))
jQuery(this).wrap("<span class='NTermWrap'></span>");
});
jQuery(".LimitGeneral").each(function(){
// avoid multiple wrapping because of ~ wikifications:
if(!jQuery(this.parentNode).hasClass("LimitGeneralWrap"))
jQuery(this).wrap("<span class='LimitGeneralWrap'></span>");
});
};
// to get green underlined, add a wrapper with the NTermWrap class; apply the styling to both wrappers (.NTermWrap) and the element itself (.NTerm)
// hide message area on click elsewhere (useful for touchscreen devices
var positiveClearMessage = function() {
clearMessage();
return true;
};
jQuery("body").on("click",positiveClearMessage);
if(!config.extensions.postponeMsg) {
config.extensions.postponeMsg = true;
config.extensions.orig_displayMessage = displayMessage;
displayMessage = function(a,b) {
var doDisplay = function() { config.extensions.orig_displayMessage(a,b); };
setTimeout(doDisplay,100);
};
}
//}}}
/***
!!!Semantics
{{{
div[tags~="code"] .editor, #tiddlerStyleSheet .editor,
div[tags~="systemConfig"] .editor
{ font-family: monospace; }
.PoG, .PoGc { color: green; }
.DDn, .DDnc { color: purple; }
.FpG, .FpGc { color: blue; }
.PoGc:before, .DDnc:before, .FpGc:before,
.c:before { content: "["; color: [[ColorPalette::Foreground]]; }
.PoGc:after, .DDnc:after, .FpGc:after,
.c:after { content: "]"; color: [[ColorPalette::Foreground]]; }
.NTerm { color: [[ColorPalette::Foreground]]; font-weight: normal; }
.NTermWrap { color: #00aa00; text-decoration: underline; font-weight: bold; }
.LimitGeneral { color: [[ColorPalette::Foreground]]; font-weight: normal; }
.LimitGeneralWrap { color: #0000ff; text-decoration: underline; font-weight: bold; }
}}}
!!!Semantics details
* possibilities of growth (including comments): {{{.PoG}}}, {{{.PoGc}}}
* done [written] to be done, but not ideally: {{{.DDn}}}, {{{.DDnc}}}
* frozen possibilities of growth or things done/written not ideally: {{{.FpG}}}, {{{.FpGc}}}
* just comments or implied things: {{{.c}}}
* {{{code}}} tag is to be used in tiddlers with code (CSS, ~JavaScript etc) which changes representation when editing
* {{PoGc{лучше перевести все описания на русский -- слишком много неопределённости}}}
* {{PoGc{стоит что-то придумать с {{{[[ColorPalette::Foreground]]}}} на случай night mode}}}
!!!Representation
{{{
#sidebarOptions .sliderPanel { background-color:[[ColorPalette::Background]] !important;
font-size: 95% !important; }
#tiddlerStyleSheet .viewer pre { clear: both !important; }
.headerForeground,
.headerShadow { padding-top: 1em !important; }
.viewer .tabContents { background-color: inherit; }
}}}
!!!Representation details
* ...
* {{{margin-bottom}}} of {{{#displayArea}}} -- to be able to keep the content on the eye level
!!!Representation tools
{{{
table.borderless,
table.borderless tr,
table.borderless td,
table.borderless th { border: 0 !important; }
div[tags~="hideTagged"]
.tagging { display: none; }
div[tags~="hideTags"]
.tagged { display: none; }
}}}
!!!Representation tools details
* ...
!!!FireFox CSS
{{{
@-moz-document url-prefix() {
pre, code, div[tags~="code"] .editor,
div[tags~="systemConfig"] .editor,
#tiddlerStyleSheet .editor
{ font-family: 'Droid Sans Mono', Consolas !important; font-size: 100% !important; }
}
}}}
!!!!
***/
/***
|Version|0.1|
|Short status|<<insertEditable container:"@stateComment" size:max cell>>|
***/
//{{{
// adapted from the 2.7.1 core:
// Sets the value of the given field of the tiddler to the value.
// Setting an ExtendedField's value to null or undefined removes the field.
// Setting a namespace to undefined removes all fields of that namespace.
// The fieldName is case-insensitive.
// All values will be converted to a string value.
TiddlyWiki.prototype.setValue = function(tiddler,fieldName,value,noNotify)
{
TiddlyWiki.checkFieldName(fieldName);
var t = this.resolveTiddler(tiddler);
if(!t)
return;
fieldName = fieldName.toLowerCase();
var isRemove = (value === undefined) || (value === null);
var accessor = TiddlyWiki.standardFieldAccess[fieldName];
if(accessor) {
if(isRemove)
// don't remove StandardFields
return;
var h = TiddlyWiki.standardFieldAccess[fieldName];
if(!h.set(t,value))
return;
} else {
var oldValue = t.fields[fieldName];
if(isRemove) {
if(oldValue !== undefined)
// deletes a single field
delete t.fields[fieldName];
else {
// no concrete value is defined for the fieldName
// so we guess this is a namespace path.
// delete all fields in a namespace
var re = new RegExp("^"+fieldName+"\\.");
var dirty = false;
var n;
for(n in t.fields)
if(n.match(re)) {
delete t.fields[n];
dirty = true;
}
if(!dirty)
return;
}
} else {
// the "normal" set case. value is defined (not null/undefined)
// For convenience provide a nicer conversion Date->String
value = value instanceof Date ? value.convertToYYYYMMDDHHMMSSMMM() : String(value);
if(oldValue == value)
return;
t.fields[fieldName] = value;
}
}
// When we are here the tiddler/store really was changed.
if(!noNotify)
this.notify(t.title,true);
if(!fieldName.match(/^temp\./))
this.setDirty(true);
};
//}}}
/***
|Version|0.11.1|
|Requires|ForEachTiddlerPlugin SetFieldPlugin|
|~|ForEachTiddlerPlugin is not required for the operation of the plugin, but if FETP is present, it should be evaluated before this plugin; also, there's no other simple way to use this plugin aside with FETP; SetFieldPlugin is required for quality (removes extra refreshing which quickens some actions and removes some representation bugs|
|Documentation|[[SetManagerPluginInfo]]|
|Author|Yakov Litvin|
***/
//{{{
// for performance improving/debugging
var timeTester = {
startTime: null,
init: function() {
this.startTime = new Date();
},
timePoints: [],
calcElapsedTime: function() {
var time = new Date() - this.startTime;
this.timePoints.push(time);
return time;
}
};
// --------------------------------------------------------------------------------
// sort counters methods ("S" stands for "sort")
Tiddler.prototype.getSCounterPlace = function(storageName)
{
storageName = storageName || config.macros.itemMenu.defaultField
var withFieldNameRegExp = /(.*?)@@(.*)/,
withFieldNameMatch = withFieldNameRegExp.exec(storageName);
return {
storageName: withFieldNameMatch ? withFieldNameMatch[1] : null,
fieldName: withFieldNameMatch ? withFieldNameMatch[2] : storageName
}
};
Tiddler.prototype.getRawSData = function(fieldName)
{
fieldName = fieldName || config.macros.itemMenu.defaultField;
if(!this.getIncludeURL || !this.getIncludeURL())
return store.getValue(this,fieldName);
};
Tiddler.prototype.getSCounter = function(fieldName)
{
var storageData = this.getSCounterPlace(fieldName),
storageName = storageData.storageName,
storageField = storageData.fieldName,
indexText;
if(!this.getIncludeURL || !this.getIncludeURL())
return parseInt(this.getRawSData(storageField));
// for included tiddlers use a separate tiddler as a stored index
if(storageName) {
var storageTiddler = store.fetchTiddler(storageName);
indexText = storageTiddler ?
storageTiddler.getRawSData(storageField) : "";
} else
indexText = store.getTiddlerText(storageField);
// find the line in the index which describes the tiddler, if present
var indexLineRegExp = config.macros.itemMenu.getIndexLineRegExp(this.title),
indexMatch = indexLineRegExp.exec(indexText);
return indexMatch ? parseInt(indexMatch[1]) : undefined;
};
Tiddler.prototype.setRawSData = function(fieldName,value)
{
fieldName = fieldName || config.macros.itemMenu.defaultField;
// reduced version of store.setValue(this,fieldName,value) for SData
TiddlyWiki.checkFieldName(fieldName);
fieldName = fieldName.toLowerCase();
if(TiddlyWiki.standardFieldAccess[fieldName])
return;
if(this.fields[fieldName] == value)
return;
this.fields[fieldName] = ""+value; // as a String, only string values are stored
store.setDirty(true);
};
Tiddler.prototype.updateSIndex = function(indexText,value)
{
// find the line in the index which describes the tiddler, if present
var indexLineRegExp = config.macros.itemMenu.getIndexLineRegExp(this.title),
indexMatch = indexLineRegExp.exec(indexText);
var newIndexLine = config.macros.itemMenu.createIndexLine(this.title,value);
if(indexMatch)
indexText = indexText.replace(indexLineRegExp,newIndexLine);
else
indexText += (newIndexLine+"\n");
return indexText;
};
Tiddler.prototype.setSCounter = function(fieldName,value)
{
var storageData = this.getSCounterPlace(fieldName),
storageName = storageData.storageName,
storageField = storageData.fieldName,
indexText;
if(!this.getIncludeURL || !this.getIncludeURL()) {
this.setRawSData(storageField,value);
return;
};
// for included tiddlers use a separate tiddler as a stored index
// for orderField@@tiddlerName syntax, use the storageName tiddler
// for storage, otherwise use the fieldName tiddler
var indexTid = store.fetchTiddler(storageName || fieldName);
if(!indexTid)
indexTid = store.createTiddler(storageName || fieldName);
if(storageName) {
indexText = indexTid.getRawSData(storageField) || "";
indexText = this.updateSIndex(indexText,value);
indexTid.setRawSData(storageField,indexText);
} else
indexTid.text = this.updateSIndex(indexTid.text,value);
};
Tiddler.prototype.deleteSCounter = function(fieldName)
{
fieldName = fieldName.toLowerCase();
if(TiddlyWiki.standardFieldAccess[fieldName])
return; // use of StandardFields is unlikely, but don't remove them anyway
if(this.getSCounter(fieldName) !== undefined)
delete this.fields[fieldName];
};
// --------------------------------------------------------------------------------
TiddlyWiki.prototype.saveModifiedTiddler = function(title, newTitle, newBody, tags, fields, clearChangeCount, created, creator) {
var tidBeingChanged = (title instanceof Tiddler) ? title : this.fetchTiddler(title);
title = tidBeingChanged.title;
var conflictingTiddler = this.fetchTiddler(newTitle) || this.fetchTiddler(title);
if(conflictingTiddler && conflictingTiddler != tidBeingChanged)
if(!confirm("A tiddler named \""+title+"\" already exists. Do you want to overwrite it?"))
return;
return this.saveTiddler(title,newTitle,newBody,
config.options.txtUserName,
new Date(),
tags,fields,clearChangeCount,created,creator)
};
var preventOtherHandling = window.preventOtherHandling = function(e)
{
// prevent propagation
if (e.stopPropagation)
e.stopPropagation();
e.cancelBubble = true;
// prevent browser action from firing
if(e.preventDefault)
e.preventDefault();
e.returnValue = false;
// see https://learn.javascript.ru/default-browser-action
}
// helps to avoid popup closing on event
var wrapNoClose = window.wrapNoClose = function(func)
{
return function(ev)
{
if(func) func.apply(this,arguments);
var e = ev || window.event; // support old IE
if(!e)
return false;
preventOtherHandling(e);
};
};
// helps to make the height of textareas appropriate (a working prototype)
//# defaultHeight should be calced as a height of one line;
//# maxHeight - not more than 1/2 or 3/4 of the screen
//# also, better to get rid of shrinking scrollbar..
var adjustHeightToContent = function()
{
var defaultHeight = 30;
var maxHeight = 400;
jQuery(this).height(defaultHeight);
jQuery(this).height(Math.min(this.scrollHeight , maxHeight));
};
// --------------------------------------------------------------------------------
// constants for using with jQuery .which()
var $tab = 9,
$enter = 13,
$esc = 27,
$delete = 46,
$home = 36,
$end = 35,
$up = 38,
$down = 40,
$pgUp = 33,
$pgDn = 34,
$plus = 61,
$r = 82,
$e = 69,
$t = 84,
$i = 73,
$x = 88;
// --------------------------------------------------------------------------------
// detecting touch-screens (see http://stackoverflow.com/a/4819886/3995261)
window.isOpenedOnTouchScreen = function()
{
return !!('ontouchstart' in this);
}
//}}}
//{{{
config.macros.itemMenu =
{
getIndexLineRegExp: function(tiddlerName) {
return new RegExp(tiddlerName.escapeRegExp() + ": ([0-9]+)");
},
createIndexLine: function(tiddlerName, value) {
return tiddlerName+": "+value;
},
defaultField: "orderCounter",
itemMenuClass: "listMenuButton",
sortByCounter: function(tiddlerArray, fieldName)
{
var defaultValue = -1; // undefinedUp (1000 for undefinedDown)
return tiddlerArray.sort(function(t1,t2){
var c1 = t1.getSCounter(fieldName), c2 = t2.getSCounter(fieldName);
c1 = (!c1 && c1 != 0) ? defaultValue : c1;
c2 = (!c2 && c2 != 0) ? defaultValue : c2;
return c1 - c2;
});
},
currentlyDragged: [],
setCurrentlyDragged: function(tidName,sortField,dropAction,itemMenuElement,
onKeyDown,onKeyUp)
{
this.currentlyDragged.push({
name: tidName,
field: sortField,
dropAction: dropAction,
itemMenuElement: itemMenuElement,
onKeyDown: onKeyDown,
onKeyUp: onKeyUp
});
if(itemMenuElement) {
jQuery(itemMenuElement).bind("keyup",onKeyUp);
jQuery(itemMenuElement).bind("keydown",onKeyDown);
}
},
clearCurrentlyDragged: function()
{
if(!this.currentlyDragged[0]) return;
var i, context, upHandler, downHandler;
for(i = 0; i < this.currentlyDragged.length; i++)
{
context = this.currentlyDragged[i];
upHandler = context.onKeyUp;
downHandler = context.onKeyDown;
if(!context.itemMenuElement) continue;
if(downHandler)
jQuery(context.itemMenuElement).unbind("keydown",downHandler);
if(upHandler)
jQuery(context.itemMenuElement).unbind("keyup",upHandler);
}
this.currentlyDragged = [];
jQuery(".buttonSortState").parent().parent().removeClass("selected");
//# check if /\ grandparent is <tr> and remove only in that case
jQuery(".buttonSortState").removeClass("buttonSortState");
},
getCurrentlyDragged: function() {
//# for now, works only with the first selection (if multiple)
return this.currentlyDragged[0] ? this.currentlyDragged[0].name : null;
},
getCurrentSourceListContext: function() {
//# for now, works only with the first selection (if multiple)
return this.currentlyDragged[0];
},
markSelected: function(itemMenu)
{
if(!itemMenu) return;
jQuery(itemMenu).addClass("buttonSortState").focus();
if(itemMenu.parentElement.tagName.toLowerCase() == "td")
jQuery(itemMenu).parent().parent().addClass("selected");
},
ensureFocusOnCurrentlyDragged: function()
{
var currentlyDragged = this.getCurrentSourceListContext();
console.log("currentlyDragged:");console.log(currentlyDragged);
if(!currentlyDragged) return;
var itemMenu = currentlyDragged.itemMenuElement;
// because of refreshing, itemMenu can be no longer attached to the ~root
// element (document? html?). If that's the case, find the new one
var newItemMenu, itemMenus = jQuery("."+this.itemMenuClass+
'[filter=\''+itemMenu.getAttribute("filter")+'\']')
.each(function(i,el){
if(el.tiddler == itemMenu.tiddler)
newItemMenu = el;
});
if(newItemMenu != itemMenu) {
// remember actual element, reattach onkeyup, onkeydown handlers
currentlyDragged.itemMenuElement = newItemMenu;
jQuery(newItemMenu).bind("keyup",currentlyDragged.onKeyUp);
jQuery(newItemMenu).bind("keydown",currentlyDragged.onKeyDown);
}
this.markSelected(newItemMenu);
},
actionStepsWithArguments: {},
actionStepsWithoutArguments: {},
applyActionStep: function(tiddler, actionStep, rootElement)
{
var actionRegExp = /(.\w+)\.\.(.*)/,
match = actionRegExp.exec(actionStep),
actionStepName;
if(match) { // action with an argument
for(actionStepName in this.actionStepsWithArguments)
if(actionStepName == match[1])
return this.actionStepsWithArguments[actionStepName](tiddler,match[2],rootElement);
//# may be throw an error/warning?
return;
} else // actionStep without arguments
for(actionStepName in this.actionStepsWithoutArguments)
if(actionStepName == actionStep)
return this.actionStepsWithoutArguments[actionStepName](tiddler,rootElement);
//# may be throw an error/warning?
return;
},
parseAndApplyAction: function(tiddler, actionLine, rootElement, noNotify)
{
if(!tiddler || !actionLine)
return;
var actionStepsArray = actionLine.split(",,");
for(var i = 0; i < actionStepsArray.length; i++)
this.applyActionStep(tiddler,jQuery.trim(actionStepsArray[i]), rootElement);
if(!noNotify)
store.notify(tiddler.title,true);
},
parseAndSeparateActions: function(actionsLine)
{
var actionsArray = actionsLine.split(";;");
if(actionsArray.length == 1 && !actionsArray[0].contains("::"))
return jQuery.trim(actionsArray[0]);
var actionMap = {},
name_and_action_RegExp = /(.+?)::(.*)/, match;
for(var i = 0; i < actionsArray.length; i++)
{
match = name_and_action_RegExp.exec(actionsArray[i]);
if(!match || !match[1] || !match[2])
//# may be throw an error/warning?
continue;
actionMap[jQuery.trim(match[1])] = jQuery.trim(match[2]);
actionMap["default"] = actionMap["default"] || jQuery.trim(match[2]);
}
return actionMap;
},
// moving helpers
checkCounters: function(filter,field)
{
var tids = store.filterTiddlers(filter);
tids = this.sortByCounter(tids, field);
for(var i = 0; i < tids.length; i++)
tids[i].setSCounter(field,i);
return tids;
},
moveToArbitraryPlace: function(filter,tiddler,field,index,doCycle)
{
var tids = this.checkCounters(filter,field), i,
tidIndex = tids.indexOf(tiddler),
tidsNum = tids.length;
// parse "top"/"bottom" values
index = (index == "top") ? 0 : (index == "bottom" ? tidsNum-1 : index);
if(doCycle) {
index = index % tidsNum;
index = (index < 0) ? (index + tidsNum) : index;
}
// do nothing in cases.. ("tidIndex < 0" = "tiddler is not in the list")
if(tidIndex == index || tidIndex < 0 || index < 0 || index >= tids.length)
return;
//timeTester.init()
//console.log("~middle of moveToArbitraryPlace: time elapsed (ms): " + timeTester.calcElapsedTime())
// move items
tiddler.setSCounter(field,index);
if(tidIndex > index)
for(i = index; i < tidIndex; i++)
tids[i].setSCounter(field,i+1);
else
for(i = index; i > tidIndex; i--)
tids[i].setSCounter(field,i-1);
//console.log("pre-end of moveToArbitraryPlace: time elapsed (ms): " + timeTester.calcElapsedTime())
// refresh the list (order)
store.notify(field,true);
//console.log("end of moveToArbitraryPlace: time elapsed (ms): "+timeTester.calcElapsedTime())
// ~200 ms for notifying / in which browser?
},
moveToTop: function(filter,tiddler,field)
{ this.moveToArbitraryPlace(filter,tiddler,field,"top"); },
moveToBottom: function(filter,tiddler,field)
{ this.moveToArbitraryPlace(filter,tiddler,field,"bottom"); },
// syntax: <<itemMenu tiddlerName filter field:fieldName addAction:actionSyntax
// dropAction:actionSyntax switchActions:actionsSyntax>>
handler: function(place,macroName,params,wikifier,paramString,tiddler)
{
// parse params: tiddlerName, context filter; field, noConflicts
var pParams = paramString.parseParams("pP",null,true,false,true),
tName = pParams[0]["pP"][0], // name of the tid governed by itemMenu
tid = store.fetchTiddler(tName),
filter = pParams[0]["pP"][1], // tids among which the sorting is done
field = getParam(pParams,"field",this.defaultField), // field which holds the counter value
addAction = getParam(pParams,"addAction",""), //
dropAction = getParam(pParams,"dropAction",""), //
switchActions = getParam(pParams,"switchActions",""); //
var cmi = config.macros.itemMenu; // shortcut
var serapartedSwitchActions = this.parseAndSeparateActions(switchActions);
var checkCounters = function()
{ return cmi.checkCounters(filter,field); };
// selecting/rearranging helpers
var cancelSelection = function()
{
cmi.clearCurrentlyDragged();
Popup.remove(); // close the popup, including on esc
};
var getSelected = function()
{
// get currently "dragged" itemMenu element
var draggedContext = cmi.getCurrentSourceListContext();
return draggedContext ? draggedContext.itemMenuElement : null;
};
// jQuery of itemMenu elements with the same filter
var getJMenus = function()
{
return jQuery("."+cmi.itemMenuClass+"[filter='"+filter+"']");
};
var reselectByIndex = function(jMenus,index) // jMenus is "jQuery(menus)"
{
var menuToSelect = jMenus[index];
if(!menuToSelect) return;
cancelSelection();
// select (focus is needed for keyboard events to fire)
jQuery(menuToSelect).focus().click();
console.log("click generated in reselectByIndex");
//# may be optimize the two previous steps (do reselection directly)
};
var selectNext = function()
{
var itemMenu = getSelected();
if(!itemMenu) return;
// other item menus, found by the same filter
var menus = getJMenus(),
currentIndex = menus.index(itemMenu);
reselectByIndex(menus,currentIndex+1);
};
var selectPrev = function()
{
var itemMenu = getSelected();
if(!itemMenu) return;
// other item menus, found by the same filter
var menus = getJMenus(),
currentIndex = menus.index(itemMenu);
reselectByIndex(menus,currentIndex-1);
};
var selectNeighbour = function()
{
var itemMenu = getSelected();
if(!itemMenu) return;
// other item menus, found by the same filter
var menus = getJMenus(),
currentIndex = menus.index(itemMenu);
if(currentIndex > 0)
reselectByIndex(menus,currentIndex-1);
else
reselectByIndex(menus,currentIndex+1);
// returns true when successfully selected another item
return getSelected() != itemMenu;
};
var selectFirst = function()
{
// other item menus, found by the same filter
var menus = getJMenus();
reselectByIndex(menus,0);
};
var selectLast = function()
{
// other item menus, found by the same filter
var menus = getJMenus();
reselectByIndex(menus,menus.length-1);
};
var moveDown = function(doReselect)
{
var tids = checkCounters(), i = tids.indexOf(tid);
if(i < 0) return; // not among tids
if(i >= tids.length-1) // out of boundaries
return doReselect ? "" : cancelSelection();
// make the switch with the neighbour
tids[i].setSCounter(field,i+1);
tids[i+1].setSCounter(field,i);
// refresh the list (order)
store.notify(field,true);
//# try to avoid extra refreshing on long press down
if(doReselect) reselectByIndex(getJMenus(),i+1);
};
var moveUp = function(doReselect)
{
var tids = checkCounters(), i = tids.indexOf(tid);
if(i < 0) return; // not among tids
if(i <= 0) // out of boundaries
return doReselect ? "" : cancelSelection();
// make the switch with the neighbour
tids[i].setSCounter(field,i-1);
tids[i-1].setSCounter(field,i);
// refresh the list (order)
store.notify(field,true);
if(doReselect) reselectByIndex(getJMenus(),i-1);
};
var moveToTop = function(doReselect)
{
cmi.moveToTop(filter,tid,field);
if(doReselect) reselectByIndex(getJMenus(),0);
};
var moveToBottom = function(doReselect)
{
cmi.moveToBottom(filter,tid,field);
var menus = getJMenus();
if(doReselect) reselectByIndex(menus,menus.length-1);
};
var moveToArbitraryPlace = function(movingTidName, above)
{ // filter, targetTidName is got via enclosure
var tids = checkCounters(),
movingTid = store.fetchTiddler(movingTidName),
targetTid = store.fetchTiddler(tName),
movingInd = tids.indexOf(movingTid),
targetInd = tids.indexOf(targetTid),
index = targetInd + ((movingInd > targetInd) ? (above ? 0 : 1) : (above ? -1 : 0));
cmi.moveToArbitraryPlace(filter,movingTid,field, index);
};
var onClickItemMenu = wrapNoClose(function(e)
{
console.log("onClickItemMenu");
console.log("e.which is "+e.which);
console.log(e);
if(!cmi.getCurrentlyDragged()) // ~drag
{
clearMessage(); // useful on smartphones
// open the popup menu:
var manageButton = this;
var popup = Popup.create(this);
jQuery(popup).addClass("itemMenu");
// then, add buttons there:
// the "cancel selection" button
createTiddlyButton(popup,"","cancel selection",
cancelSelection,"cancelSelectionButton button");
// switch action(s) button
var createActionButtonInPopup = function(place,actionName)
{
var li = createTiddlyElement(place,"li"),
action = serapartedSwitchActions[actionName];
createTiddlyButton(li,actionName,"",function(){
cmi.parseAndApplyAction(tid,action,this);
cancelSelection();
},"listedDoActionButton button");
};
var bringActions = wrapNoClose(function() {
if(serapartedSwitchActions["default"])
{
// named actions
var dPopup = Popup.create(this), li, actName;
for(actName in serapartedSwitchActions) {
if(actName == "default")
continue;
createActionButtonInPopup(dPopup, actName);
}
Popup.show("bottom","left");
} else { // single unnamed action is defined
cmi.parseAndApplyAction(tid,switchActions, this);
cancelSelection();
}
cmi.clearCurrentlyDragged();
// no class removing since refreshing is applied
});
if(serapartedSwitchActions && tid)
createTiddlyButton(popup,"","drop this item",
bringActions,"doActionButton button");
// tag toggler
var macroText = "<<tagToggler [["+tName+"]] \"\">>";
wikify(macroText, popup);
var tagButton = popup.lastChild,
startTagToggling = function() {
jQuery(tagButton).click();
console.log("click generated in startTagToggling");
};
// the "info" button
var showTiddlerInfo = wrapNoClose(function(){
var infoPopup = Popup.create(popup);
createTiddlyText(infoPopup,"references:");
config.commands.references.handlePopup(infoPopup, tid.title);
Popup.show("bottom","left");
return false;
});
createTiddlyButton(popup,"","tiddler info", showTiddlerInfo,"tiddlerInfoButton button");
// the "rename" button
var startRenaming = wrapNoClose(function(){
var renamePopup = Popup.create(popup),
li = createTiddlyElement(renamePopup,"li"),
initTitle = tid.title,
nameField = createTiddlyElement(li,"textarea",null,"nameInput"),
changeName = function(doSave,goOnSelected)
{
var newTitle = jQuery.trim(nameField.value);
store.saveModifiedTiddler(initTitle,newTitle);
story.refreshTiddler(tid.title,null,true);
if(doSave) autoSaveChanges();
if(!goOnSelected)
cancelSelection();
else
cmi.ensureFocusOnCurrentlyDragged();
};
nameField.value = tid.title;
createTiddlyButton(li,"rename","rename \""+tid.title
+"\"",changeName,"button renameButton");
nameField.onclick = wrapNoClose(); // prevent popup closing
// press enter to apply or esc to exit
// (shift and ctrl are also taken into account)
nameField.onkeydown = function(ev)
{
var e = ev || window.event;
if(e.which == $enter) {
changeName(e.ctrlKey,e.shiftKey);
Popup.remove(1); // renamePopup only
window.preventOtherHandling(e);
}
if(e.which == $esc) {
if(e.shiftKey) {
cmi.ensureFocusOnCurrentlyDragged();
Popup.remove(1);
window.preventOtherHandling(e);
} else
cancelSelection();
}
};
Popup.show("bottom","left");
nameField.focus(); // put the cursor inside the name edit area
});
createTiddlyButton(popup,"","rename this tiddler", startRenaming,"renameTiddlerButton button");
// the "edit text" button
var startEditing = wrapNoClose(function()
{
var textEditPopup = Popup.create(popup),
li = createTiddlyElement(textEditPopup,"li"),
textField = createTiddlyElement(li,"textarea",null,"ttextInput"),
changeText = function(e)
// e is either click or keydown (enter) event
{
var goOnSelected = e.shiftKey;
tid.set(null,textField.value);
store.saveModifiedTiddler(tid);
story.refreshTiddler(tid.title,null,true);
autoSaveChanges();
if(goOnSelected)
cmi.ensureFocusOnCurrentlyDragged();
else
cancelSelection();
};
textField.value = tid.text;
// change the width of the popup and hence of {{{textField}}}
textEditPopup.style.width = "100%";
createTiddlyButton(li,"save","save the text of \""+
tid.title+"\"",changeText,
"button saveTextButton");
textField.onclick = wrapNoClose(); // prevent popup closing
// press enter to apply or esc to exit
// (shift and ctrl are also taken into account)
textField.onkeydown = function(ev)
{
var e = ev || window.event;
if(e.ctrlKey && e.which == $enter) {
changeText(e);
Popup.remove(1); //textEditPopup only
window.preventOtherHandling(e);
}
if(e.which == $esc) {
if(e.shiftKey) {
cmi.ensureFocusOnCurrentlyDragged();
Popup.remove(1);
window.preventOtherHandling(e);
} else {
Popup.remove();
cancelSelection();
}
}
};
textField.onkeypress = function(e)
{
adjustHeightToContent.apply(this);
// avoid firing of story.onTiddlerKeyPress
if(e.which == $enter) {
if(e.stopPropagation)
e.stopPropagation();
e.cancelBubble = true;
}
// don't return false to insert linebreaks
// on enter (when typing)
}
Popup.show("bottom","left");
adjustHeightToContent.apply(textField);
textField.focus(); // put the cursor inside the text edit area
});
createTiddlyButton(popup,"","edit the tiddler text",
startEditing,"editTiddlerTextButton button");
// the "open" button
var i = getTiddlyLinkInfo(tName,"openTiddlerButton button"),
openTiddler = function(e) {
onClickTiddlerLink(e);
cancelSelection();
},
openButton = createTiddlyButton(popup,"","open "+tName,
openTiddler,i.classes,null,null,{
refresh: "link", tiddlyLink: tName
}), // see createTiddlyLink source
clickOpenTiddler = function(){
jQuery(openButton).click();
console.log("click generated in clickOpenTiddler");
};
// "add" button
var cma = config.macros.addTiddler;
if(cma && addAction) {
var btn = createTiddlyButton(popup,"",
"add tiddler here",cma.onClick,
"addTiddlerButton button"),
currentIndex = getJMenus().index(this);
btn.params = { title:"", text:"", commonTags:[],
addAction: addAction,
orderCounter: field,
orderFilter: filter,
orderMode: currentIndex
}
var addTiddler = function(e) { jQuery(btn).click();
window.preventOtherHandling(e);
console.log("click generated in addTiddler"); };
}
// the "delete" button
var confirmDeleteMsg = config.commands.deleteTiddler.warning,
deleteTiddler = function(e,goOnSelected)
{
if(e) // on click
goOnSelected = goOnSelected || e.shiftKey;
if(confirm(confirmDeleteMsg.format([tName]))) {
if(goOnSelected)
goOnSelected = selectNeighbour();
// if no neighbour, nothing to reselect
store.removeTiddler(tName);
autoSaveChanges();
}
if(goOnSelected)
cmi.ensureFocusOnCurrentlyDragged();
else
cancelSelection();
};
createTiddlyButton(popup,"","delete this tiddler",
deleteTiddler,"deleteTiddlerButton button");
// support keyboard
// keypress ignores "delete", arrows etc, hence keydown
var onKeyDownItemMenu = function(e) {
switch(e.which) {
case $enter: clickOpenTiddler();break;
case $down:
if(e.ctrlKey) moveDown(true);
else selectNext();
break;
case $up:
if(e.ctrlKey) moveUp(true);
else selectPrev();
break;
case $home:
if(e.ctrlKey) moveToTop(true);
else selectFirst();
break;
case $end:
if(e.ctrlKey) moveToBottom(true);
else selectLast();
break;
case $esc:
// onkeydown here, because onkeyup ~can't be
// prevented from ~propagation in subpopups
if(!e.shiftKey)
cancelSelection();
else
//for the info and tag popups
Popup.remove(1);
break;
//# add keyboard support for moving to the beginning/end (ctrl+)"Home"/"End"
}
// allow browser tab switching
if(e.which != $tab || !e.ctrlKey)
return false;
};
var onKeyUpItemMenu = function(e) {
switch(e.which) {
case $i: showTiddlerInfo(); break;
case $r: startRenaming(); break;
case $e: startEditing(); break;
case $t: startTagToggling(); break;
case $plus:
if(cma && addAction)
addTiddler(e);
break;
case $delete: case $x:
// tries to reselect if e.shiftKey!
deleteTiddler(null,e.shiftKey);
break;
//# add keyboard support for actions D[A?]
}
return false;
};
cmi.setCurrentlyDragged(tName, field, dropAction, this, onKeyDownItemMenu, onKeyUpItemMenu);
cmi.markSelected(this);
/*# create array of { handler: .. , label: .. , activationKeys: .. }
and try to make button creation, keyboard support "for each ..", extendable set of .. */
Popup.show("top","right");
} else { // ~drop
// if taken from another list, the item must be changed by the drop+add actions
var dragListContext = cmi.getCurrentSourceListContext(),
dropActionNow = dragListContext.dropAction,
/* not 100% accurate */ sameList = (dropActionNow == dropAction) && (dragListContext.field == field),
dropFromOtherList = !sameList && (dropActionNow || addAction),
dropAndAddAction = (dropActionNow && addAction) ?
(dropActionNow+",,"+addAction)
: (dropActionNow || addAction);
if(dropFromOtherList)
{
var draggedTid = store.fetchTiddler(cmi.getCurrentlyDragged());
cmi.parseAndApplyAction(draggedTid,dropAndAddAction);
draggedTid.deleteSCounter(dragListContext.field);
}
// move the item
Popup.remove(); // close the popup
if(cmi.getCurrentlyDragged() == tName) {
jQuery(this).removeClass("buttonSortState");
moveDown(); // on drop-on-self
} else
moveToArbitraryPlace(cmi.getCurrentlyDragged(),true);
// refreshing removes the "buttonSortState" class
cmi.clearCurrentlyDragged();
}
});
// create the 2 sets of classes for the button and for the table row
// (if itemMenu is inside a td element) based on the tiddler's tags
var itemMenuClasses = this.itemMenuClass,
rowClasses = "",
badSymbolsRE = /[\.,;:`!@#\$%\^&\*\(\)\+=\[\]\{\}\|\\/'"~ ]/g;
for(var i = 0 ; tid && i < tid.tags.length ; i++) {
// process each tag: substitute spaces and symbols .,;:!'"()[]{}=+\|/*&^%$#@`~ with "_"
itemMenuClasses += " marker_"+ tid.tags[i].replace(badSymbolsRE,"_");
rowClasses += "line_"+ tid.tags[i].replace(badSymbolsRE,"_")+" ";
}
// create the button(s) // text is set via CSS
var btn = createTiddlyButton(place,"", "",onClickItemMenu,itemMenuClasses);
btn.setAttribute("filter",filter);
btn.tiddler = tid;
// in table, add the line_className classes based on the tiddler's tags
if(place.tagName.toLowerCase() == "td")
jQuery(place).parent().addClass(rowClasses);
}
}
// define the actions
// add tag
config.macros.itemMenu.actionStepsWithArguments["+tag"] = function(tiddler,tag)
{
if(!tiddler.isTagged(tag)) {
tiddler.tags.push(tag);
tiddler.modifier = config.options.txtUserName;
tiddler.modified = new Date();
}
};
// remove tag
config.macros.itemMenu.actionStepsWithArguments["-tag"] = function(tiddler,tag)
{
if(tiddler.isTagged(tag)) {
tiddler.tags.splice(tiddler.tags.indexOf(tag),1);
tiddler.modifier = config.options.txtUserName;
tiddler.modified = new Date();
}
};
// toggle tag
config.macros.itemMenu.actionStepsWithArguments["!tag"] = function(tiddler,tag)
{
if(tiddler.isTagged(tag))
tiddler.tags.splice(tiddler.tags.indexOf(tag),1);
else
tiddler.tags.push(tag);
tiddler.modifier = config.options.txtUserName;
tiddler.modified = new Date();
};
// set field
config.macros.itemMenu.actionStepsWithArguments["setField"] = function(tiddler,argument)
{
var argRE = /(\w+)\.\.(.*)/, argMatch = argRE.exec(argument);
if(!argMatch) return;
var field = argMatch[1], value = argMatch[2];
value = value || null; // to remove the field if empty
store.setValue(tiddler,field,value,true);
};
// delete field
config.macros.itemMenu.actionStepsWithArguments["deleteField"] = function(tiddler,argument)
{
store.setValue(tiddler,argument,null,true);
};
//config.macros.itemMenu.actionStepsWithArguments["<name>"] = function(tiddler,arg) {
//};
// save changes
config.macros.itemMenu.actionStepsWithoutArguments["save"] = function() {
saveChanges();
};
config.macros.itemMenu.actionStepsWithoutArguments["delete"] = function(tiddler) {
var confirmDeleteMsg = config.commands.deleteTiddler.warning;
if(confirm(confirmDeleteMsg.format([tiddler.name])))
{
store.removeTiddler(tiddler.title);
autoSaveChanges();
}
};
//config.macros.itemMenu.actionStepsWithoutArguments["<name>"] = function(tiddler) {
//};
// implement the styling
var iMenuClass = config.macros.itemMenu.itemMenuClass;
config.shadowTiddlers["StyleSheetManualSorter"] = "/*{{{*/\n"+
".editTTextItem { width: 100% }\n"+
".ttextInput { width: 99%; margin-right: 0.5%; margin-left: 0.5%; }\n\n"+
"."+iMenuClass+" { color: inherit; font-weight: bold; }\n"+
"."+iMenuClass+":hover { background-color: inherit; color: green; }\n"+ // use ColorPalette?
"."+iMenuClass+":before { content: \"⊙\"; }\n"+
".cancelSelectionButton:before { content: \"⊗\"; }\n"+
".doActionButton:before { content: \"D\"; }\n"+
".tiddlerInfoButton:before { content: \"I\"; }\n"+
".renameTiddlerButton:before { content: \"R\"; }\n"+
".editTiddlerTextButton:before { content: \"E\"; }\n"+
".openTiddlerButton:before { content: \"O\"; font-weight: normal; }\n"+
".addTiddlerButton:before { content: \"+\"; }\n"+
".deleteTiddlerButton:before { content: \"X\"; }\n"+
".itemMenu .tagTogglerButton:before { content: \"T\"; }\n"+
".itemMenu .button { padding-left: 0.25em; padding-right: 0.25em; }\n"+
".buttonSortState:hover,\n"+
".buttonSortState { background-color: inherit; color: #00B000; }\n"+
// for "lists" with buttons as "markers" (using tables)
".tableList,\n"+
".tableList > tbody,\n"+
".tableList > tbody > tr,\n"+
".tableList > tbody > tr > td,\n"+
".tableList .button { border: none !important; }\n"+
".tableList td { vertical-align: top; padding: 0px 3px; }\n"+
".tableList ul,\n"+
".tableList ol,\n"+
"li .tableList,\n"+
".tableList .tableList { margin: 0; }\n"+
".tableList > tbody > .selected { background-color: #ddddff; }\n\n"+
// full width of table with minimal width of the first column
".tableList { width: calc(100% - 2em); }\n"+ // 2em of margins on both sides
".tableList > tbody > tr > td:first-child { width: 1px; }\n"+
/* is some cases
* .tableList td { width: 1px; }
* .tableList td:last-child { width: 100%; }
* would be more suitable */
"/*}}}*/";
store.addNotification("StyleSheetManualSorter", refreshStyles);
//}}}
/***
!!!Tagging helpers and tagToggler macro
***/
//{{{
config.filters.existingTags = function(results,match)
{
var allTags = store.getTags();
for(var i = 0; i < allTags.length; i++)
allTags[i] = allTags[i][0];
if(match[3] == "-") {
for(i = 0; i < results.length; i++)
if(!allTags.contains(results[i].title))
results.splice(i--,1);
return results;
}
for(var i = 0; i < allTags.length; i++)
allTags[i] = store.fetchTiddler(allTags[i]) || new Tiddler(allTags[i]);
// default action: add all the tags
for(i = 0; i < allTags.length; i++)
results.pushUnique(allTags[i]);
return results;
};
config.filters.tagging = function(results,match)
{
var prefix = match[3].substr(0,1), title = match[3].substr(1), tid = store.fetchTiddler(title),
tagging = [], notTagging = [];
if(!tid)
return [];
//# this behaviour may be changed after some testing
for(var i = 0; i < results.length; i++)
if(tid.tags.contains(results[i].title))
tagging.push(results[i]);
else
notTagging.push(results[i]);
switch(prefix) {
case ">":
return tagging.concat(notTagging)
case "<":
return notTagging.concat(tagging)
case "+":
return tagging
case "-":
return notTagging
}
displayMessage("Warning: the \"tagging\" filter must be used with one of the prefixes +, -, > or <, which is not the case.");
// use cookie to decide whether to suppress the message?
return results;
};
config.macros.tagToggler = {};
config.macros.tagToggler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
// parse params
var pParams = paramString.parseParams("pParams",null,true,false,true),
tid = store.fetchTiddler(pParams[0]["pParams"][0]) || tiddler; // tid. which tags will be toggled
if(!tid) return;
var label = pParams[0]["pParams"][1];
if(label == "." || label == undefined)
label = "toggle tags";
var tooltip = getParam(pParams,"tooltip","toggle tags of "+tid.title),
doAutoSave = params.contains('doAutoSave') || false,
tagsSet = getParam(pParams,"tags","[existingTags[+]] [tagging[>"+tid.title+"]]");
// for compability with SetManagerPlugin
var cmi = config.macros.itemMenu,
clearSelected = function()
{
if(cmi) cmi.clearCurrentlyDragged();
},
returnItemMenuSelection = function()
{
if(cmi) cmi.ensureFocusOnCurrentlyDragged();
};
var whereToScroll;
var toggleTag = function(tid,tag,refreshTagListIfNotClosing)
{
if(tid.isTagged(tag))
tid.tags.splice(tid.tags.indexOf(tag),1);
else
tid.tags.push(tag);
if(refreshTagListIfNotClosing) return refreshTagListIfNotClosing();
store.saveModifiedTiddler(tid);
story.refreshTiddler(tid.title,null,true);
if(doAutoSave)
autoSaveChanges();
if(whereToScroll)
window.scrollTo(0,ensureVisible(whereToScroll));
clearSelected();
};
// define the onclick handlers
var onTagClick = wrapNoClose(function(e)
{
var tag = this.getAttribute("tag"),
shiftWasHold = (e || window.event).shiftKey;
toggleTag(tid,tag,shiftWasHold ? this.refreshTagList : null);
return false;
});
var onClick = wrapNoClose(function()
{
// form the list of the tags to choose from, all tags to start somewhere
var availableTags = [],
allTags = store.getTags(),
noTagsMsg = "No tags found by the provided criterion";
for(var i = 0; i < allTags.length; i++)
availableTags.push(allTags[i][0]);
// build the list of tags and labels
var tagsToWorkWith = store.filterTiddlers(tagsSet);
for(i = 0; i < tagsToWorkWith.length; i++)
tagsToWorkWith[i] = tagsToWorkWith[i].title;
var menuTags = [], menuLabels = [], menuItems = [], t;
for(i = 0; i < tagsToWorkWith.length; i++) {
t = tagsToWorkWith[i];
menuItems.push({
tag: t,
label: t // '[x] ' or '[ ] ' addition is now defined via css
})
}
// create the popup menu
var popup = Popup.create(this), li, tagButton;
// arbitrary tag toggler
li = createTiddlyElement(popup,"li");
var newTagField = createTiddlyElement(li,"input",null,"newTagInput",{type:"text"});
newTagField.onclick = wrapNoClose(); // prevent the popup from closing on click here
var selectedTagIndex = 0, // 0 means "not selected"
selectNextTag = function()
{
var nextListItem = popup.childNodes[selectedTagIndex+1];
if(!nextListItem) return // out of boundaries, won't move
jQuery(popup).children().children().removeClass("selectedTag");
selectedTagIndex++;
nextListItem.childNodes[0].classList.add("selectedTag");
},
selectTag15ahead = function()
{
var numberOfItems = jQuery(popup).children().length; // tags+1
selectedTagIndex = Math.min(selectedTagIndex + 15,numberOfItems-1);
var newListItem = popup.childNodes[selectedTagIndex];
jQuery(popup).children().children().removeClass("selectedTag");
newListItem.childNodes[0].classList.add("selectedTag");
},
selectPrevTag = function()
{
var prevListItem = popup.childNodes[selectedTagIndex-1];
if(selectedTagIndex == 0) return;
jQuery(popup).children().children().removeClass("selectedTag");
selectedTagIndex--;
if(selectedTagIndex == 0) return; // don't color the main field
//# or do so but color it in the beginning as well
prevListItem.childNodes[0].classList.add("selectedTag");
},
selectTag15back = function()
{
var numberOfItems = jQuery(popup).children().length; // tags+1
selectedTagIndex = Math.max(selectedTagIndex - 15,0);
var newListItem = popup.childNodes[selectedTagIndex];
jQuery(popup).children().children().removeClass("selectedTag");
if(selectedTagIndex > 0)
newListItem.childNodes[0].classList.add("selectedTag");
},
selectTag = function(tag)
{
var button = jQuery(popup).children().children("*[tag='"+tag+"']");
if(!button.length) return;
button.addClass("selectedTag");
// get index, set selectedTagIndex
selectedTagIndex = jQuery(popup).children().children().index(button)
- 1;
},
getSelectedTagButton = function() {
if(selectedTagIndex == 0) // fosuced on new tag field
return null;
return popup.childNodes[selectedTagIndex].childNodes[0];
},
toggleSelected = function(goOnToggling)
{
var button = getSelectedTagButton();
if(!button) return;
tag = button.getAttribute("tag");
fieldValue = newTagField.value;
toggleTag(tid,tag,goOnToggling ? refreshTagList : null);
newTagField.value = fieldValue;
newTagField.select();
selectTag(tag);
return tag;
},
toggleNew = function(goOnToggling) {
var tag = jQuery.trim(newTagField.value), isNewTag = true, i;
// add the tag to the list if it's totally new:
for(i = 0; i < menuItems.length; i++)
if(menuItems[i].tag == tag)
isNewTag = false;
if(isNewTag)
menuItems.push({ tag:tag, label:tag });
toggleTag(tid,tag,goOnToggling ? refreshTagList : null);
};
// push the button to apply/click elsewhere to cancel..
createTiddlyButton(li,"toggle","toggle the entered tag in the tiddler",
toggleNew,"button tagAdderButton");
// ..or use keyboard (see below)
// tags from the set
var refreshTagList = function()
{
//console.log("caller is " + arguments.callee.caller.toString());
// clear the list (but don't remove the first item)
while(popup.childNodes[1])
popup.removeChild(popup.childNodes[1]);
newTagField.focus();
selectedTagIndex = 0;
// refill the list
if(menuItems.length == 0) {
createTiddlyText(createTiddlyElement(popup,"li"),noTagsMsg);
return;
}
var fieldValueLowered = newTagField.value.toLocaleLowerCase(),
sortedMenuItems = menuItems.concat([]).sort(function(a,b){
if(!fieldValueLowered) return 0;
// store where the value from the field starts in the tag
a.index= a.tag.toLocaleLowerCase().search(fieldValueLowered);
b.index= b.tag.toLocaleLowerCase().search(fieldValueLowered);
// if(tid.tags.contains(a.tag) && a.index != -1) a.index = -2;
// if(tid.tags.contains(b.tag) && b.index != -1) b.index = -2;
return a.index > b.index;
}),
item;
for(i = 0; i < sortedMenuItems.length; i++)
{
item = fieldValueLowered ? sortedMenuItems[i] : menuItems[i];
if(fieldValueLowered && item.index == -1) continue;
li = createTiddlyElement(popup,"li");
tagButton = createTiddlyButton(li,item.label,
"toggle '"+item.tag+"'",onTagClick,"button tag"+
(tid.tags.contains(item.tag)? "" : "Not")+"Present");
tagButton.setAttribute("tag",item.tag);
tagButton.refreshTagList = refreshTagList;
}
};
refreshTagList();
// show the popup menu
Popup.show("bottom","left");
// support keyboard navigation
jQuery(newTagField).bind('input',function(e)
{
refreshTagList();
});
jQuery(newTagField).bind('keyup',function(e)
{
goOnToggling = e.shiftKey;
if(e.which == $enter) {
toggleSelected(goOnToggling) || toggleNew(goOnToggling);
if(!goOnToggling) Popup.remove();
//document.getElementById("displayArea").focus()
return;
}
if(e.which == $esc) {
if(whereToScroll)
window.scrollTo(0,ensureVisible(whereToScroll));
if(e.shiftKey) {
returnItemMenuSelection();
Popup.remove(1);
//# make sure doesn't get propagated?
} else {
clearSelected();
Popup.remove();
}
//document.getElementById("displayArea").focus()
}
});
jQuery(newTagField).bind('keydown',function(e)
{
if(e.which == $down && !e.ctrlKey)
selectNextTag();
if(e.which == $up && !e.ctrlKey)
selectPrevTag();
if(e.which == $pgDn || (e.which == $down && e.ctrlKey))
selectTag15ahead();
if(e.which == $pgUp || (e.which == $up && e.ctrlKey))
selectTag15back();
if(selectedTagIndex)
window.scrollTo(0,ensureVisible(getSelectedTagButton()));
else
window.scrollTo(0,ensureVisible(newTagField));
});
});
// create the button
whereToScroll = createTiddlyButton(place,label,tooltip,onClick,"button tagTogglerButton");
}
// set styling
config.shadowTiddlers["TagAdderStyleSheet"] = "/*{{{*/\n"+
".tagPresent:before { content: \"[x] \"; }\n"+
".tagNotPresent:before { content: \"[ ] \"; }\n"+
".newTagInput { float: left; margin-right: 0.5em; }\n"+
".tagAdderButton { text-align: center; }\n"+
".selectedTag { color: blue !important; }\n"+
"/*}}}*/";
store.addNotification("TagAdderStyleSheet", refreshStyles);
//}}}
/***
!!!Hijack forEachTiddler macro to enable the new params
***/
//{{{
// helper filter for hiding hidden tiddlers
config.filters.hideFromFet = function(results,match)
{
var contextName = match[3], noContext = contextName == "-";
for(var i = 0; i < results.length; i++)
if(results[i].fields["hideInFet".toLowerCase()] &&
(results[i].fields["hideInFet".toLowerCase()] && noContext ||
results[i].fields["hideInFet".toLowerCase()]
.split(" ").contains(contextName)))
results.splice(i--,1);
return results;
};
// hijack config.macros.forEachTiddler.parseParams so that it handles
// "set", "sortable"/"sortableBy", "addAction", "dropAction", "switchAction" params
// the "params" array is not changed as its usages in parseParams don't need it,
// same story for preParsedParams[i] (i > 0)
if(config.macros.forEachTiddler && !config.macros.forEachTiddler.hijacked_sortable)
{
//# check if the proper version of FET (1.3.0 or above) is used
config.macros.forEachTiddler.hijacked_sortable = true;
config.macros.forEachTiddler.oldFashionParams =
config.macros.forEachTiddler.oldFashionParams.concat([
"sortableBy", "addAction", "dropAction", "switchActions", "writeToList"]);
config.extensions.ManualSortMacroPlugin = {
orig_fet_parseParams: config.macros.forEachTiddler.parseParams
};
var origParse = config.extensions.ManualSortMacroPlugin.orig_fet_parseParams;
config.macros.forEachTiddler.parseParams = function(preParsedParams,params)
{
// parse the "set" param
var setDescription = getParam(preParsedParams,"set",""), filter, sortField,
setAddAction, setDropAction, cmd = config.macros.defineSet;
if(setDescription) {
if(!setDescription.contains("[")) {
filter = "[set["+ setDescription +"]]"; // named set
sortField = cmd.getSortFieldForNamedSet(setDescription);
setAddAction = cmd.getAddToNamedSetAction(setDescription);
setDropAction= cmd.getDropFromNamedSetAction(setDescription);
} else {
filter = "set: "+setDescription; // inline set
var setDefinition = cmd.parseSetDefinition(setDescription);
// don't overwrite setDefinition as it is passed to adder
sortField = setDefinition.sortField;
setAddAction = cmd.getAddToSetAction(setDefinition);
setDropAction = cmd.getDropFromSetAction(setDefinition);
}
sortField = sortField || config.macros.itemMenu.defaultField;
}
// remember the filter (calc from both "set" and "filter" params)
var filterParam = getParam(preParsedParams,"filter","") +
(setDescription ? " [hideFromFet[-]]" : "");
if(filter && filterParam) {
if(filter.indexOf("set: ") == 0) {
filter = filter + " modify: " + filterParam;
} else
filter = filter + " " + filterParam;
} else if(filterParam)
filter = filterParam;
if(!filter)
return origParse.apply(this,arguments);
// the "in", "where" params stay untouched; change the filter param
preParsedParams[0]["filter"] = [filter];
// hijack the "script" param (define the "insert" and "adder" helpers)
var usedScript = getParam(preParsedParams,"script",""),
insertDefinition = "var insert = "+
"function(container,params,defaultText,preprocessScript) {"+
"container = container || 't';"+
"params = params || '';"+
"if(defaultText)"+
"params = 'showIfNoValue:\\''+defaultText+'\\' '+params;"+
"if(preprocessScript)"+
"params = 'preprocess:\\''+preprocessScript+'\\''+params;"+
"return '<<insertEditable tiddler:['+'['+tiddler.title+']] container:\"'+container+'\" '+(params||'')+'>>';"+
"};";
adderDefinitionBegin = 'var adder = '+
'function(args,label,orderMode,title) {'+
'return "<<addTiddler"'+
' +(" label:\'"+(label || "+")+"\'")'+
' '+(setDescription ? ('+" set:\''+setDescription+'\'"') : ''),
adderDefinitionEnd =
' +(" title:\'"+(title || "")+"\'")'+ // empty by default
' +" "+(args||"")'+
' +">>"'+
'};',
adderDefinition = adderDefinitionBegin + adderDefinitionEnd,
// unless sortableBy is defined, orderMode is ignored (see below)
fullScript = insertDefinition + adderDefinition + usedScript;
preParsedParams[0]["script"] = [fullScript];
// process and apply sortable/sortableBy params/sortField from set definition
var sortableParamIndex = params.indexOf("sortable"),
justSortable = sortableParamIndex != -1,
sortableBy = getParam(preParsedParams,"sortableBy");
if(params.contains("sortableBy") && !sortableBy)
return { errorText: "Field name expected behind 'sortableBy'."};
if(justSortable)
sortableBy = config.macros.itemMenu.defaultField;
// support the deprecated {{{sortableBy '"orderCountName"'}}} syntax
var fieldWithQuotsMatch = /^"(.+)"$/.exec(sortableBy);
sortableBy = fieldWithQuotsMatch ? fieldWithQuotsMatch[1] : sortableBy;
// sortField can be defined directly or from the set (see above)
sortField = sortableBy || sortField;
//# rethink from here: either move this stuff below actions parsing etc (more meaninglful)
// or add "&& !setDescription" (this is to enable actions and other stuff for sets,
// even if sortField is not defined)
//+ from here
if(!justSortable && !sortField)
return origParse.apply(this,arguments);
// support the "extended scope for sorting"
//# is it extended or narrowed?
var fieldAndFilterMatch = /^(\w+) (\[.+)$/.exec(sortField),
sortFilter = fieldAndFilterMatch ? fieldAndFilterMatch[2] : filter;
sortField = fieldAndFilterMatch ? fieldAndFilterMatch[1] : sortField;
// set the "sortBy" param
var undefinedUp = true;
var sortScript = "(function(){ var c = tiddler.getSCounter(\"" + sortField +
"\"); return (c != 0 && !c)?" + (undefinedUp ? "-1" : "1000") + ": c; })()";
// lists of 999+ tiddlers long are not supposed to be used with manual sorting
preParsedParams[0]["sortBy"] = [sortScript];
// the sortable/sortableBy part is left in preParsedParams[0] as is
// extend the "adder" helper in the "script" param using specified sortField
adderDefinition = adderDefinitionBegin +
' +(orderMode ? (" order:\''+sortField+','
+filter.replace(/"/g,'\\"')
//# do smth about "'"s in filter (macro param parsing)
+',"+orderMode+"\'") : "")'+
adderDefinitionEnd;
fullScript = insertDefinition + adderDefinition + usedScript;
preParsedParams[0]["script"] = [fullScript];
//= up to here
// for actions other than "write" (and "writeToList" ~action), do no more
for(var knownActionName in config.macros.forEachTiddler.actions)
if(knownActionName != "write" && params.contains(knownActionName))
return origParse.apply(this,arguments);
// in original FETP, that's "addToList" action only
// parse the [SMP] actions-defining params
var addAction = getParam(preParsedParams,"addAction",setAddAction),
dropAction = getParam(preParsedParams,"dropAction",setDropAction),
switchActions = preParsedParams ? (
preParsedParams[0]["switchActions"] ?
preParsedParams[0]["switchActions"].join(";;")
: ""
) : "";
// allow multiple switchActions params (but each must have a name..)
var commonText = "description is expected behind";
if(!addAction && !(addAction == "") && params.contains("addAction"))
return { errorText: "An action "+commonText+" 'addAction'." };
if(!dropAction && !(dropAction == "") && params.contains("dropAction"))
return { errorText: "An action "+commonText+" 'dropAction'." };
if(!switchActions && !(switchActions=="")&& params.contains("switchActions"))
return { errorText: "An action(s) "+commonText+" 'switchActions'."};
// parse [FET] action
var action = "writeToList"; // default pseudo-action
// when action is not specified it is considered as writeToList with..
var defaultText = '"["+"["+tiddler.title+"]]"';
//# unknown actions are considered as the default one.. which is bad for other extensions
// when "writeToList" is used, in fact it preparses the argument for "write"
writeToListText = getParam(preParsedParams,"writeToList",defaultText);
var writeText = '"| "+itemMenu()+" |"+(' +writeToListText+ ')+"|\\n"';
// when "write" is used, its argument is used after only "minimal"preparsing
if(preParsedParams[0]["write"])
action = "write";
writeText = getParam(preParsedParams,"write",writeText);
// substitute all the "itemMenu()" expressions in the argument of "write"
// with their intended "meaning" (hence use non-greedy regexp)
var itemMenuRegExp = /(.*?)itemMenu\(\)/g;
var insertItemMenu = function($0,$1) {
var escapedFilter = sortFilter.contains('"') ?
('\\\''+sortFilter.replace(/"/g,'\\\"')+'\\\'') :
("\\\""+sortFilter.replace(/'/g,"\\\'")+"\\\"") ;
// this is a semi-fix: tags with both ' and " will cause troubles with manual sorting..
// escape the actions as well
return $1 + "\"<<itemMenu [[\"+tiddler.title+\"]] "+ escapedFilter +
(sortField ? (" field:\\\""+ sortField +"\\\"") : "")+
(addAction ? " addAction:\\\""+addAction+"\\\"" : "")+
(dropAction ? " dropAction:\\\""+dropAction+"\\\"" : "")+
(switchActions ? " switchActions:\\\""+switchActions+"\\\"" : "")+">>\"";
}
writeText = writeText.replace(itemMenuRegExp,insertItemMenu);
// change preParsedParams accordingly (use writeText, "write" action)
preParsedParams[0]["write"] = [writeText];
// change the begin argument (leave end, none, toFile parts unchanged)
if(action == "writeToList")
preParsedParams[0]["begin"] = [(preParsedParams[0]["begin"] ?
preParsedParams[0]["begin"][0]:'""') + '+"|tableList|k\\n"'];
// call the parser with the new arguments
return origParse.apply(this,arguments);
}
}
//}}}
/***
!!!addTiddler macro
***/
//{{{
config.macros.addTiddler = {
handler: function(place,macroName,params,wikifier,paramString) {
if(readOnly)
return;
// param parsing (partially taken from the newTiddler macro)
params = paramString.parseParams("anon",null,true,false,false);
var title = params[1] && params[1].name == "anon" ? params[1].value : config.macros.newTiddler.title;
title = getParam(params,"title",title);
var label = getParam(params,"label",label),
prompt = getParam(params,"prompt",config.macros.newTiddler.prompt),
text = getParam(params,"text",""),
set = getParam(params,"set",""),
commonTags = [], t,
orderParts = getParam(params,"order",""),
orderPartsMatch = /^(\w*),(.+),([\w\d\-]+(?:,\w+)?)$/.exec(orderParts),
orderCounter = orderPartsMatch ? orderPartsMatch[1] : undefined,
orderFilter = orderPartsMatch ? orderPartsMatch[2] : undefined,
orderMode = orderPartsMatch ? orderPartsMatch[3] : undefined,
orderParamDefault;
if(orderMode) {
orderPartsMatch = /^(\w+),(\w+)$/.exec(orderMode);
orderMode = orderPartsMatch ? orderPartsMatch[1] : orderMode;
orderParamDefault = orderPartsMatch ? orderPartsMatch[2] : undefined;
}
var cmd = config.macros.defineSet;
// get addAction for the set and orderCounter
if(set && cmd) {
// set may be either set name or set definition
if(!set.contains("[")) {
orderCounter = orderCounter ||
cmd.getSortFieldForNamedSet(set);
var action = cmd.getAddToNamedSetAction(set);
} else {
var setDefinition = cmd.parseSetDefinition(set);
orderCounter = orderCounter || setDefinition.sortField;
var action = cmd.getAddToSetAction(setDefinition)
}
}
for(t = 1; t < params.length; t++)
if((params[t].name == "anon" && t != 1) || (params[t].name == "tag"))
commonTags.push(params[t].value);
if((orderCounter =="default" || orderCounter =="") && config.macros.itemMenu)
orderCounter = config.macros.itemMenu.defaultField;
// create button, attach params to it
var btn = createTiddlyButton(place,label,prompt,this.onClick);
btn.params =
{
title: title,
commonTags: commonTags,
addAction: (set && cmd) ? action : "",
text: text,
orderCounter: orderCounter,
orderFilter: orderFilter,
orderMode: orderMode,
orderParamDefault: orderParamDefault
};
},
onClick: window.wrapNoClose(function()
{
// extract params
var title = this.params.title,
text = this.params.text,
tags = [].concat(this.params.commonTags), // should be a new array
//# do the same "copying" for fields, if are set here
addAction = this.params.addAction,
orderCounter = this.params.orderCounter,
orderFilter = this.params.orderFilter,
orderMode = this.params.orderMode,
orderParamDefault = this.params.orderParamDefault,
// create DOM
popup = Popup.create(this),
wrapper = createTiddlyElement(popup,"li"),
nameField = createTiddlyElement(wrapper,"input",null,"newTitleInput",{type:"text"});
nameField.onclick = window.wrapNoClose();
nameField.value = title;
var cmi = config.macros.itemMenu,
addTidToSet = function(tiddler)
{
if(!addAction || !cmi) return;
cmi.parseAndApplyAction(tiddler, addAction);
};
var createTheTiddler = function(goOnSelected)
{
var theTiddler = new Tiddler(jQuery.trim(nameField.value)),
modifier = config.options.txtUserName;
theTiddler.assign(null,text,modifier,null,tags);
if(store.fetchTiddler(theTiddler.title) && !confirm("A tiddler named \""+theTiddler.title+"\" already exists. Do you want to overwrite it?"))
return;
store.saveTiddler(theTiddler);
//# var theTiddler = new Tiddler(jQuery.trim(nameField.value));
//# store.saveModifiedTiddler(theTiddler,null,text,tags);
addTidToSet(theTiddler);
if(orderCounter && Tiddler.prototype.setSCounter && cmi)
if(orderMode == "top")
cmi.moveToTop(orderFilter, theTiddler, orderCounter);
if(orderMode == "bottom")
cmi.moveToBottom(orderFilter, theTiddler, orderCounter);
var orderModeIndex = parseInt(orderMode);
if(!isNaN(orderModeIndex))
cmi.moveToArbitraryPlace(orderFilter,theTiddler, orderCounter,orderModeIndex,true);
// use: orderParamDefault
autoSaveChanges();
// for compability with SetManagerPlugin (usage in itemMenus)
if(cmi && !goOnSelected) cmi.clearCurrentlyDragged();
};
// process enter/esc key presses
// compatible with SetManagerPlugin (for usage in itemMenus)
nameField.onkeydown = function(ev)
{
var e = ev || window.event;
if(e.which == $enter) {
createTheTiddler(e.shiftKey);
Popup.remove();
if(e.shiftKey && cmi)
cmi.ensureFocusOnCurrentlyDragged();
window.preventOtherHandling(e);
}
if(e.which == $esc) {
if(e.shiftKey) {
if(cmi) cmi.ensureFocusOnCurrentlyDragged();
Popup.remove(1);
} else {
if(cmi) cmi.clearCurrentlyDragged();
Popup.remove();
}
}
};
if(orderMode == "checkboxes") {
// add possibilities to put the tiddler on top/bottom of a certain list (certain orderCounter):
// create 2 checkboxes (t: [] b: []), add .. behaviour
createTiddlyText(popup,"t:");
var checkBoxTop = createTiddlyElement(popup,"input","test"/*null*/,null,null,{
type:'checkbox',
value:false
// calc the value the way it should be calced (chkAddToTop, ..)
});
checkBoxTop.onclick = window.wrapNoClose(function(){
checkBoxTop.setAttribute(!checkBoxTop.value);
// checkboxes should deactivate each other, ..
});
// add the onclick handler (change .., no close)
createTiddlyText(popup,"b:");
config.macros.option.handler(popup,"option",null,wikifier,"chkAddToBottom");
checkBox = popup.lastChild;
checkBox.onclick = window.wrapNoClose(checkBox.onclick);
// this works, but the checkbox being checked/unchecked is not displayed unless the popup is reopened
// - try config.macros.option.genericCreate(place,type,opt,className,desc)
// process the orderCounter taken from the check box
}
// "ok" button
createTiddlyButton(popup,"ok","create the tiddler",createTheTiddler);
// cancel - on click elsewhere
// show the popup menu, focus inside the text field
Popup.show();
nameField.focus();
nameField.select()
})
}
//}}}
/***
!!!insertEditable macro
***/
//{{{
// Sets the section value if it is present, appends otherwise
// tip: if sectionName is "!!smth", then "!!!smth" is appended
//
Tiddler.prototype.setSection = function(sectionName,value)
{
var beginSectionRegExp = new RegExp("(^!{1,6}[ \t]*" + sectionName.escapeRegExp() + "[ \t]*(\n|$))","mg"),
sectionTerminatorRegExp = /^!/mg,
match = beginSectionRegExp.exec(this.text);
if(match) // edit existing section
{
var sectionTitle = match[1],
emptyAtEnd = match[2] != "\n",
beforeSection = this.text.substr(0,match.index),
sectionAndAfter = this.text.substr(match.index + match[1].length);
match = sectionTerminatorRegExp.exec(sectionAndAfter);
var afterSection = match ? sectionAndAfter.substr(match.index) : "";
this.text = beforeSection + sectionTitle + (emptyAtEnd ? "\n" : "") + value
+ (afterSection ? ("\n" + afterSection) : "");
} else // add anew
this.text = this.text + "\n!"+sectionName + "\n"+value;
// setting dirty, notifying is not done here
};
// Sets the slice value if it is present, otherwise appends it as |name|value|
// either after the last slice or to the beginning of the text (if no slices are present)
//
Tiddler.prototype.setSlice = function(sliceName,value)
{
var replaceSliceSubPart = function(text,part,oldValue)
{
if(oldValue == value)
return text;
var eOldValue = oldValue.escapeRegExp(),
eSliceName = sliceName.escapeRegExp();
// "table" notation
var simplifiedPattern = "^(.*"+eSliceName+".*\\|.*)"+eOldValue+"(.*\\|)$",
simplifiedRegExp = new RegExp(simplifiedPattern),
newPart = part.replace(simplifiedRegExp,function($0,$1,$2){
return $1 + value + $2;
});
if(newPart != part)
return text.replace(part, newPart);
// "sliceName: sliceValue" notation
simplifiedPattern = "^(.*"+eSliceName+"\\:[\\s\\t])"+eOldValue+"(.*)$";
simplifiedRegExp = new RegExp(simplifiedPattern),
newPart = part.replace(simplifiedRegExp,function($0,$1,$2){
return $1 + value + $2;
});
if(newPart != part)
return text.replace(part, newPart);
};
// modification of TiddlyWiki.prototype.slicesRE to process "|..sliceName..||" syntax
// empty slices in the "sliceName:" notation are not supported
var re = /(?:^([\'\/]{0,2})~?([\.\w]+)\:\1[\t\x20]*([^\n]+)[\t\x20]*$)|(?:^\|\x20?([\'\/]{0,2})~?([^\|\s\:\~\'\/]|(?:[^\|\s~\'\/][^\|\n\f\r]*[^\|\s\:\'\/]))\:?\4[\x20\t]*\|[\t\x20]*([^\n\t\x20]?(?:[^\n]*[^\n\t\x20])?)[\t\x20]*\|$)/gm;
re.lastIndex = 0;
var m = re.exec(this.text);
while(m) {
if(m[2]) {
if(m[2] == sliceName) {
this.text = replaceSliceSubPart(this.text,m[0],m[3]);
break;
}
} else {
if(m[5] == sliceName) {
this.text = replaceSliceSubPart(this.text,m[0],m[6]);
break;
}
}
m = re.exec(this.text);
}
if(!m || !m[2] && !m[5]) // if the slice is not present
{
// append after the last slice/to the start of text (adapted from GridPlugin)
var matches = this.text.match(re),
lastSlice = matches ? matches[matches.length-1] : null,
where = lastSlice ? this.text.indexOf(lastSlice)+lastSlice.length : 0;
this.text = this.text.substr(0,where)+
(lastSlice ? '\n|%0|%1|' : '|%0|%1|\n').format(sliceName,value)+
this.text.substr(where);
}
// recalc "stored" slices for this tiddler:
delete store.slices[this.title];
// setting dirty, notifying is not done here
};
config.macros.insertEditable = {
handler: function(place,macroName,params,wikifier,paramString,tiddler)
{
// parse and attach params to DOM
var pParams = paramString.parseParams("tiddler",null,true,false,true),
cell = params.contains("cell"),
fill = cell || params.contains("fillElement"),
wrapper = fill ? place : createTiddlyElement(place,"span"),
tidName = getParam(pParams,"tiddler",""),
partName = getParam(pParams,"container","t"),
applyOnEnter = getParam(pParams,"applyOnEnter");
wrapper.options = {
tiddler: tidName ? store.fetchTiddler(tidName) : tiddler,
part: partName,
viewType: getParam(pParams,"viewType","wikified"),
withButton: params.contains("button") || window.isOpenedOnTouchScreen(), // default for touch screens
defaultShowText: getParam(pParams,"showIfNoValue"),
preprocess: getParam(pParams,"preprocess",""),
size: getParam(pParams,"size",""),
fill: fill,
transparentEmpty: cell || params.contains("transparentEmpty"),
saveOnApply: params.contains("saveOnApply"),
applyOnEnter: (partName[0] == ":") || (partName[0] == "!") ||
(applyOnEnter === undefined ?
params.contains("applyOnEnter") : applyOnEnter),
keepOnBlur: params.contains("keepOnBlur"),
noNotify_partial: cell || params.contains("noNotify"),
noedit: params.contains("noedit")
};
this.turnViewMode(wrapper);
},
getData: function(tiddler, part)
{
var partName = part.substr(1);
switch(part[0]) {
case "t":
return tiddler.text || "";
case "!":
return tiddler.title;
case "#":
return (tiddler.getSection ? tiddler.getSection(partName)
: store.getTiddlerText(tiddler.title+"##"+partName))
|| "";
case ":":
return (tiddler.getSlice ? tiddler.getSlice(partName)
: store.getTiddlerText(tiddler.title+"::"+partName))
|| "";
case "@":
return store.getValue(tiddler,partName) || "";
}
},
setData: function(tiddler, part, value, noNotify_partial)
{
var partName = part.substr(1);
//# deal with the case when tiddler doesn't exist yet
switch(part[0]) {
case "t":
store.saveTiddler(tiddler,null,value);
break;
case "!":
store.saveTiddler(tiddler,value); // requires my fix to .sT
break;
case "#":
tiddler.setSection(partName,value);
store.setDirty(true);
// refresh display of the corresponding tiddler:
if(!noNotify_partial) store.notify(tiddler.title,true);
break;
case ":":
tiddler.setSlice(partName,value);
store.setDirty(true);
if(!noNotify_partial) store.notify(tiddler.title,true);
break;
case "@":
store.setValue(tiddler,partName,value);
break;
}
//# change the "modifier/d" fields?
},
turnViewMode: function(place)
{
if(place.options.part[0] != "c" && !place.options.tiddler)
return;
//# may be add warning; also, do same for unsupported container types
var value = this.getData(place.options.tiddler, place.options.part) ||
place.options.defaultShowText || "",
fill = place.options.fill,
noedit = place.options.noedit,
classEmpty = place.options.transparentEmpty ? "transparentEmptyViewer" : "emptyViewer";
var preprocessScript = place.options.preprocess;
if(preprocessScript) {
var fullScript =
"place.options.preprocessFunc = function(text){"+
"var q = \"'\"\n"+
preprocessScript+
"\nreturn text;};";
eval(fullScript);
value = place.options.preprocessFunc(value);
}
if(fill)
place.style.padding = (place.options.initialPadding !== undefined)
? place.options.initialPadding
: place.style.padding;
if(fill && !noedit && !value)
place.classList.add(classEmpty);
var html = '<span'+
(!fill && !value ? ' class="'+classEmpty+'"' : '')+
'></span>';
place.innerHTML = html;
place.onclick = function(e)
{
// prevent editor-containing popup closing etc:
if(e.stopPropagation) e.stopPropagation();
e.cancelBubble = true;
// prevent editing when clicking on links,buttons,images; if noedit:
if(noedit || e.target.tagName in {A:1,IMG:1}) return true;
place.classList.remove(classEmpty);
config.macros.insertEditable.turnEditMode(this);
return false;
}
var container = fill ? place : place.firstChild;
switch(place.options.viewType) {
case "plain":
createTiddlyText(container,value);
break;
case "html":
container.innerHTML = value;
break;
case "wikified":
default:
wikify(value,container,null,place.options.tiddler);
}
},
turnEditMode: function(place) {
var applyChanges = function()
{
config.macros.insertEditable.setData(place.options.tiddler, place.options.part, editarea.value, place.options.noNotify_partial);
config.macros.insertEditable.turnViewMode(place);
if(place.options.saveOnApply)
autoSaveChanges();
},
applyingChanges = false,
value = this.getData(place.options.tiddler, place.options.part),
rowslimit = 1,
size = place.options.size,
html = (size == "minimal" || size == "min") ?
'<input type="text" class="mini-inline-editor"></input>'
:('<textarea class="inline-editor"' +
' style="height: '+rowslimit+'.1em;'+
(size == "max" ? 'width:100%;' :
(place.options.fill ? 'width:98%;' : ''))+'"' +
'></textarea>'),
button;
place.innerHTML = html;
place.onclick = null;
if(place.options.withButton) {
button = createTiddlyButton(place,"save",null,applyChanges);
// fires before onblur, allowing to save:
button.onmousedown = function(){applyingChanges = true;};
}
if(place.options.fill) {
place.options.initialPadding = place.style.padding;
place.style.padding = "0";
}
var editarea = place.firstChild;
editarea.value = value;
editarea.onkeydown = function(ev)
{
var e = ev || window.event;
if(e.which == $esc) {
// for now, don't check if was changed
config.macros.insertEditable.turnViewMode(place);
jQuery('html').off("click",switchToViewOnClick);
}
if((e.which == $enter) && (e.ctrlKey || place.options.applyOnEnter))
{
applyChanges();
e.cancelBubble = true;
if(e.stopPropagation) e.stopPropagation();
return false;
}
};
// on click outside the edit area, switch to the view mode (useful for touchscreens)
var inside = false,
switchToViewOnClick = function(e) {
if(applyingChanges || inside || place.options.keepOnBlur) {
inside = false;
return;
}
config.macros.insertEditable.turnViewMode(place);
jQuery('html').off("click",switchToViewOnClick);
};
jQuery('html').click(switchToViewOnClick);
//# avoid creating of a "global" handler? (to remove extra code from .onkeydown handler)
jQuery(editarea).click(function() {
inside = true;
});
//# add handlers for touch? (ph: no ctrl+enter)
editarea.focus();
if(!window.isOpenedOnTouchScreen()) // with FF for Android, better not to
editarea.select();
editarea.onkeypress = adjustHeightToContent;
adjustHeightToContent.apply(editarea);
}
};
// define styles
var bgColor = store.getTiddlerText("ColorPalette::Background");
setStylesheet(
'.emptyViewer { color: #dddddd; background-color: #dddddd; }\n'+
'.emptyViewer:before, .transparentEmptyViewer:before { content: "__" }\n'+
'.mini-inline-editor { width: 1.5em; }\n'+
'@-moz-document url-prefix() {.inline-editor { font-family: Consolas !important; font-size: 100% !important; }}\n'+
'.transparentEmptyViewer { color: '+bgColor+'; background-color: '+bgColor+'; }',
"StyleSheetInsertEditable");
//# think about better styling (no "__" when copying)
// =========== Extras ===========
// hijack edit macro to make tiddler titles editable inline
(function(){
/* config.macros.chkEditableTitles = (config.macros.chkEditableTitles === undefined) ?
!('ontouchstart' in window) : config.macros.chkEditableTitles;
// default for non-touch screens for now
if(!config.macros.chkEditableTitles)
return;
config.macros.view.iep_orig_handler = config.macros.view.handler;
config.macros.view.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
if(readOnly || !(tiddler instanceof Tiddler) || tiddler.isReadOnly() ||
params[0] != "title" || !place.classList.contains("title"))
// the last is a hack for avoiding insertEditable in the <<list>> macro
return this.iep_orig_handler.apply(this,arguments);
wikify('<<insertEditable container:"!" size:max viewType:plain>>',place,null,tiddler);
//story.setDirty(tiddler.title,true); ?
};
*/
})()
//}}}
/***
!!!Sets API and macros
***/
//{{{
// overwrite filterTiddlers to enable different kinds of hijacking
//# to be incorporated into the core
TiddlyWiki.prototype.filterTiddlers = function(filter,results)
{
var re = /([^\s\[\]]+)|(?:\[([ \w\.\-]+)\[([^\]]+)\]\])|(?:\[\[([^\]]+)\]\])/mg;
results = results || [];
var match, handler;
if(filter)
while(match = re.exec(filter)) {
handler = ( match[1] || match[4] ) ? 'tiddler' :
config.filters[match[2]] ? match[2] : 'field';
results = config.filters[handler].call(this,results,match);
}
return results;
};
// extendable set of elementary sets definitions
// "?" is for the belongCheck, "+" - for addAction getter, "-" - for dropAction getter
config.elementarySets =
{
tiddler: {
"?": function(title,tiddler) {
return tiddler.title == title;
},
"+": function(title) {
return ""; // no action for now
},
"-": function(title) {
return ""; // no action for now
}
},
tag: {
"?": function(tagName,tiddler) {
return tiddler.tags.contains(tagName);
},
"+": function(tagName) {
//# check the absence of ",,","::",";;"
return "+tag.."+tagName;
},
"-": function(tagName) {
//# check the absence of ",,","::",";;"
return "-tag.."+tagName;
}
}
}
config.macros.defineSet =
{
sets: {},
add: function (setName, setDefinition, setTags)
{
if(this.sets[setName])
displayMessage("the set \""+setName+"\" will be redifined");
//# this behaviour may be changed if necessary
this.sets[setName] = { definition: setDefinition, tags: setTags };
},
// this returns a function(tiddler) which checks if the tiddler is in the set;
// if there's some "do" parts, it ignores them if forceFunc and
// returns an array of tiddlers instead of a function otherwise
//
getIsInSet: function (setDefinition,forceFunc)
{
var returnFunc = true, definedPart, resultRecursive,
parts = setDefinition.parts, type, i,
singleTokenRE = /(?:(\w*)\[((?:[^\]]|(?:\]\]))*)\])/,
tokenMatch, tokenType, tokenValue;
//# precalc setDefinition.parts[i].definedPart in .parseSetDefinition? (although func/not func stuff ..)
// for each part..
for(i = 0; i < parts.length; i++)
{
definedPart = null;
// process tokens first
if(parts[i].token)
{
tokenMatch = singleTokenRE.exec(parts[i].token);
//# single tokens first; .oO when multiple are needed, implement parsing
if(!tokenMatch) continue;
tokenType = tokenMatch[1];
tokenValue = tokenMatch[2];
definedPart = {
type: tokenType,
value: tokenValue
};
// process elementaries with corresponding handlers
if(config.elementarySets[tokenType]) {
definedPart.checkTiddler = function(tiddler) {
return config.elementarySets[this.type]["?"]
(this.value, tiddler);
};
parts[i].definedPart = definedPart;
continue;
}
//# process non-elementary tokens,
// for "set" and "setsTagged", launch recursively,
// if returns an array of tids instead of a function
// and !forceFunc, set returnFunc = false
// in contrast to inline sets, named sets can cause infinite loops..
}
// next, process "sets" (defined for brackets)
if(parts[i].set)
{
resultRecursive = this.getIsInSet(parts[i].set,forceFunc);
parts[i].definedPart = resultRecursive instanceof Function ?
{ checkTiddler: resultRecursive } : resultRecursive;
// in the latter case resultRecursive is an array with tids
continue;
}
//# for each "do" if !forceFunc, /skip/ it;
// set returnFunc = false otherwise
}
if(returnFunc) {
// combine checks from parts via setDefinition.combine (example: "+4*1-3+5")
return function(tiddler) {
var re = /([\+\-\*])(\d+)/g, m, isGood = false, val;
//console.log(".combine: "+setDefinition.combine+", parts:");console.log(parts);
//console.log("find definedParts in parts[i].definedPart");
while(m = re.exec(setDefinition.combine)) {
i = parseInt(m[2]);
val = parts[i].definedPart.checkTiddler(tiddler);
//console.log(i+": val is "+val+", isGood is "+isGood);
switch(m[1])
{
case "+": isGood = isGood || val; break;
case "*": isGood = isGood && val; break;
case "-": isGood = isGood && !val; break;
}
}
return isGood;
};
} //else
//# build and return an array of tiddlers
},
getIsInNamedSet: function (setName,forceFunc)
{
var set = this.sets[setName];
if(!set) return null;
return this.getIsInSet(set.definition,forceFunc);
},
getSetTiddlers: function (setDefinition,results)
{
results = results || [];
var check = this.getIsInSet(setDefinition);
if(!check)
return results;
if(check instanceof Function)
store.forEachTiddler(function(tName,tiddler) {
if(check(tiddler)) results.pushUnique(tiddler);
});
else
// check is not a function, but an array of tiddlers
for(var i = 0; i < check.length; i++)
results.pushUnique(check[i]);
return results;
},
getNamedSetTiddlers: function (setName,results)
{
var set = this.sets[setName];
if(!set) return results;
return this.getSetTiddlers(set.definition,results);
},
calcActionStepsInDefinition: function (setDefinition)
{
var parts = setDefinition.parts, i,
singleTokenRE = /(?:(\w*)\[((?:[^\]]|(?:\]\]))*)\])/,
tokenMatch, tokenType, tokenValue;
// for each part
for(i = 0; i < parts.length; i++)
{
// create descriptions of 2 actions sequences:
// one adds a tiddler to the set, another removes the tiddler from it
// process tokens first
if(parts[i].token)
{
tokenMatch = singleTokenRE.exec(parts[i].token);
//# single tokens first; .oO when multiple are needed, implement parsing
if(!tokenMatch) continue;
tokenType = tokenMatch[1];
tokenValue = tokenMatch[2];
// process elementaries with corresponding handlers
if(config.elementarySets[tokenType]) {
//# add "don't recalc if already calced"
parts[i].addAction = config.elementarySets[tokenType]
["+"](tokenValue);
parts[i].dropAction= config.elementarySets[tokenType]
["-"](tokenValue);
continue;
}
//# process non-elementary tokens,
//# ...
}
// next, process "sets" (defined for brackets)
if(parts[i].set)
{
//# add "don't recalc if already calced"
parts[i].addAction = this.getAddToSetAction(parts[i].set);
parts[i].dropAction= this.getDropFromSetAction(parts[i].set);
continue;
}
//# do anything about "do"s?
}
return;
},
getAddToSetAction: function (setDefinition)
{
// combine the already calced actions into one
var re = /([\+\-\*])(\d+)/g, m, i, partActions, actions = "";
while(m = re.exec(setDefinition.combine))
{
i = parseInt(m[2]);
partActions = setDefinition.parts[i];
switch(m[1])
{
case "+":
if(actions || !partActions.addAction) continue;
// unless that's the ~first action~, do nothing
// (we suppose that if one describes a set like
// "this OR that", than add action adds to "this"
actions = partActions.addAction;
break;
case "*":
if(!partActions.addAction) continue;
if(actions) actions += ",,";
actions += partActions.addAction;
break;
case "-":
if(!partActions.dropAction) continue;
if(actions) actions += ",,";
actions += partActions.dropAction;
break;
}
}
return actions;
},
getAddToNamedSetAction: function (setName)
{
var set = this.sets[setName];
if(!set) return null;
return this.getAddToSetAction(set.definition);
},
getDropFromSetAction: function (setDefinition)
{
// combine the already calced actions into one
var re = /([\+\-\*])(\d+)/g, m, i, partActions, actions = "";
while(m = re.exec(setDefinition.combine))
{
i = parseInt(m[2]);
partActions = setDefinition.parts[i];
switch(m[1])
{
// case "*": do nothing (if a tiddler is droped from "a",
// it is dropped from "a OR b"
// case "-": same (consider "a" and "a AND NOT b")
case "+":
if(!partActions.dropAction) continue;
if(actions) actions += ",,";
actions += partActions.dropAction;
break;
}
}
return actions;
},
getDropFromNamedSetAction: function (setName)
{
var set = this.sets[setName];
if(!set) return null;
return this.getDropFromSetAction(set.definition);
},
getSortFieldForNamedSet: function (setName)
{
return this.sets[setName] ? this.sets[setName].definition.sortField : null;
},
parseSetDefinition: function (text)
{
var set = { parts: [], combine: null, sortField: null };
// remember tokens (..[..]..[..]...), substitute them with their numbers
var tokenRegExp = /(?:\w*\[(?:[^\]]|(?:\]\]))*\])+/, // "]]" = escaped "]"
tokenMatch, origTokenText, tokenText,
sortFieldRegExp = /sortField\[(.*)\]/, sortFieldMatch;
while(tokenMatch = tokenRegExp.exec(text))
{
origTokenText = tokenMatch[0];
tokenText = origTokenText[0]=="[" ?
("tiddler"+origTokenText) : origTokenText;
sortFieldMatch = sortFieldRegExp.exec(tokenText);
if(sortFieldMatch) {
text = text.replace(origTokenText,"");
set.sortField = sortFieldMatch[1];
} else {
text = text.replace(origTokenText,set.parts.length);
set.parts.push({ token: tokenText });
}
}
// find first-level brackets, add definitions, substitute in text
var openPosition = text.indexOf("("), i, level = 0, closePosition, setText;
while(openPosition > -1)
{
// find closing bracket position
level = 1; i = openPosition+1;
while(level > 0) {
if(text[i] == "(")
level++;
if(text[i] == ")")
level--;
i++;
}
closePosition = i;
// add definition, parse it recursively, subsititute
setText = text.substring(openPosition+1, closePosition-1)
// substitute numbers in setText back with tokens:
.replace(/\d+/g,function(match){
return set.parts[parseInt(match)].token
});
set.parts.push({ set: this.parseSetDefinition(setText) });
text = text.substring(0,openPosition) + (set.parts.length-1)
+ text.substring(closePosition);
// find next open
openPosition = text.indexOf("(");
}
// find <num> DO <num>, add them to parts, substitute
var doRegExp = /(\d+)\s+DO\s+(\d+)/m, doMatch, target, action;
while(doMatch = doRegExp.exec(text))
{
action = parseInt(doMatch[2]); target = parseInt(doMatch[1]);
action = set.parts[action].token;
target = set.parts[target].token || target;
// add the definition part, substitute the DO expression in the text
set.parts.push({ do: action , to: target });
text = text.replace(doMatch[0],set.parts.length-1);
}
set.combine = "+"+text.replace(/\s+AND\s+/g,"*")
.replace(/\s+NOT\s+/g,"-")
.replace(/\s+OR\s+/g,"+")
.replace(/^ +/,"").replace(/ +$/,"").replace(/ +/g,"+");
this.calcActionStepsInDefinition(set);
return set;
},
handler: function(place,macroName,params,wikifier,paramString,tiddler)
{
// parse params
var parsedParams = paramString.parseParams("name",null,true,false,true),
setName = getParam(parsedParams,"name"),
setText = getParam(parsedParams,"tids"),
setSortField = getParam(parsedParams,"sortField",""),
setTagsLine = getParam(parsedParams,"tags",""),
setTags = setTagsLine.readBracketedList();
if(!setName || !setText)
return;
if(setSortField) setText += " sortField["+ setSortField +"]";
var setDefinition = this.parseSetDefinition(setText);
// show macro text
var w = wikifier, macroTWcode = w.source.substring(w.matchStart,w.nextMatch),
hide = getFlag(parsedParams, "hide", false) || params.contains('hide');
if (!hide)
createTiddlyText(createTiddlyElement(place,"code"),macroTWcode);
// define the set
this.add(setName, setDefinition, setTags);
}
}
// hijack filterTiddlers so that if there's "set:..." part with an optional terminator
// ":set", then that part is parsed as a definition of a set
TiddlyWiki.prototype.ds_orig_filterTiddlers = TiddlyWiki.prototype.filterTiddlers;
TiddlyWiki.prototype.filterTiddlers = function(filter,results)
{
var beginSetMark = "set:", endSetMark = " modify:";
// set definition starts with "set:", if no such thing, use ordinary filtering
if(filter.indexOf(beginSetMark) != 0)
return this.ds_orig_filterTiddlers(filter,results);
// add tiddlers from the set
results = results || [];
var modifyPos = filter.indexOf(endSetMark), filterAsWell = (modifyPos != "-1"),
setDef = filterAsWell ? filter.substring(4,modifyPos) : filter.substr(4),
tids = config.macros.defineSet.getSetTiddlers(
config.macros.defineSet.parseSetDefinition(setDef),results);
// if necessary, apply the additional filters, return
if(!filterAsWell)
return results;
filter = filter.substr(modifyPos + endSetMark.length);
return this.ds_orig_filterTiddlers(filter,results);
};
config.filters.set = function(results,match)
{
var setName = match[3];
return config.macros.defineSet.getNamedSetTiddlers(setName,results);
};
//-------------------------------------------------------------------------------
// wikify SetsList on startup
//
var readSetsList = function()
{
if(!window.store)
return setTimeout(readSetsList,100);
var setsList = store.fetchTiddler("SetsList"),
setsListText = setsList ? setsList.text : "";
if(setsListText)
wikify(setsListText,document.createElement("div"),null,setsList);
};
setTimeout(readSetsList,100);
//# test why this first timeout is needed (copied from CTP, STP)
//}}}
/***
|Requires|ForEachTiddlerPlugin SetManagerPlugin|
|Version|0.4|
этот плагин предназначен для дополнительных elementary sets, switch actions и др. необязательных расширений SetManagerPlugin
* ForEachTiddlerPlugin нужен, пока {{{.getSlice}}} и {{{.getSection}}} не определены в ядре
to do<<tiddler [[to do list template##main]] with:"set extras todo" with:orderSEtodo noedit>>
***/
//{{{
(function(){
if(!config.macros.itemMenu)
return;
config.macros.itemMenu.actionStepsWithoutArguments["markChanged"] = function(tiddler)
{ // for coordinator
tiddler.modifier = config.options.txtUserName;
tiddler.modified = new Date();
};
config.elementarySets.hasPart =
{
"?": function(param,tiddler)
{
var regExpText, re, type;
switch(param.substr(0,2)) {
case config.textPrimitives.sectionSeparator: // ##
return !!(tiddler.getSection(param.substr(2)));
case config.textPrimitives.sliceSeparator: // ::
return !!(tiddler.getSlice(param.substr(2)));
case "@@":
return !!(tiddler.fields[param.substr(2).toLowerCase()]);
case "r@": // regExp (for tiddler.text) mode
case "R@":
case "t@": // title mode
case "T@":
regExpText = store.getTiddlerText(param.substr(2));
type = param.substr(0,1);
if(!regExpText) {
if(type == "r" || type == "t")
return true; // "forgiving mode", nothing is filtered out in this case
else
throw("RegExp for checkTiddler is not found in " + param.substr(2));
//# test this case out
}
// no break here
case "r[":
case "R[":
case "t[":
case "T[":
if(!regExpText) {
regExpText = param.substr(2);
type = param.substr(0,1);
}
if(type == "r" || type == "t") {
try {
re = new RegExp(regExpText);
} catch(e) {
return false; // "forgiving mode"
}
} else
re = new RegExp(regExpText);
if(type == "r" || type == "R")
return !!(tiddler.text.match(re));
else
return !!(tiddler.title.match(re));
}
return false;
},
"+": function(param) {
return ""; // for now, no idea what to do here; this won't matter frequently
},
"-": function(param) {
return ""; // for now, no idea what to do here; this won't matter frequently
}
};
config.macros.itemMenu.actionStepsWithoutArguments["-tags"] = function(tiddler) {
tiddler.tags = [];
};
config.elementarySets.taggedOnly =
config.elementarySets.oTag = {
"?": function(tag,tiddler) {
return (tiddler.tags.length == 1) && (!tag || tag == tiddler.tags[0]);
},
"+": function(tag) {
return "-tags,,+tag.."+tag;
},
"-": function(tag) { return "-tag.."+tag; }
};
config.elementarySets.unclassified = {
//# implemented "as was"; may be scanning the whole tree instead
"?": function(metaTag,tiddler) {
var tags = [], tag, i;
for(i = 0; i < tiddler.tags.length; i++) {
tag = store.fetchTiddler(tiddler.tags[i]);
if(tag && tag.tags.contains(metaTag))
return false;
}
return true;
},
//# implement
"+": function(metaTag) {
return ""; /*<addToSetAction> - show "available" tags, ask for a tag to add
(may be new; in that case create a tag-tid with metaTag) */
},
"-": function(metaTag) {
return "" /*<dropFromSetAction> - remove tags tagged with metaTag*/;
}
};
//config.macros.itemMenu.actionStepsWithArguments["<name>"] = function(tiddler,arg) {
//};
//config.macros.itemMenu.actionStepsWithoutArguments["<name>"] = function(tiddler) {
//};
//config.elementarySets["<name>"] = {
// "?": function(param,tiddler) { return <isInSet> },
// "+": function(param) { return <addToSetAction> },
// "-": function(param) { return <dropFromSetAction> }
//}
})()
//}}}
/***
|''Name''|SharedTiddlersPlugin|
|''Description''|Introduces a possibility to use tiddlers from other ~TiddlyWikis (with or without importing them)|
|''Documentation''|http://yakovl.bplaced.net/TW/STP/STP.html#SharedTiddlersPluginInfo|
|''Type''|plugin|
|''Version''|2.4.0"'|
|''~CoreVersion''|2.2.6|
|''Requires''|UpToDateFiltersPlugin|
|''Requirements note''|[[UpToDateFiltersPlugin|http://yakovl.bplaced.net/TW/ExtraFilters.html#UpToDateFiltersPlugin]] is necessary only for TW below v2.6.2|
|''Source''|http://yakovl.bplaced.net/TW/STP/STP.html#SharedTiddlersPlugin|
|''Author''|Yakov Litvin|
|''Forked from''|[[IncludePlugin|http://tiddlywiki.abego-software.de/#IncludePlugin]], by Udo Borkowski|
|''Contact''|see [[docs|SharedTiddlersPluginInfo]]|
|''Copyright''|Yakov Litvin, 2013|
|''Licence''|[[BSD-like open source license|http://yakovl.bplaced.net/TW/STP/STP.html#%5B%5BYakov%20Litvin%20Public%20Licence%5D%5D]] |
|>| In this tiddler, the code is minified and hidden; the full code can be found [[here|http://yakovl.bplaced.net/TW/STP/STP.html#SharedTiddlersPluginCode]]. |
''Config:''
***/
//{{{
config.options.STP_hijackPrettyLink = true;
config.options.STP_hijackImageFormatter = true;
//}}}
// /%
config.filters.all = function(results,match) {
if(match[3] == "with included") { // brings included tiddlers as well
var m,matched = this.reverseLookup();
for(m = 0; m < matched.length; m++)
results.pushUnique(matched[m]);
} else
this.forEachTiddler(function(tName,tiddler){
results.pushUnique(tiddler);
});
return results;
};
config.filters.includedFrom = function(results,match) {
var url = twWeb.getStoreUrlById(match[3]);
if(!url)
return [];
for(var i = 0; i < results.length; i++)
if(results[i].getIncludeURL() != url)
results.splice(i--,1);
return results;
};
config.filters.external = function(results,match) {
for(var i = 0; i < results.length; i++)
if(!results[i].getIncludeURL())
results.splice(i--,1);
return results;
};
config.filters.internal = function(results,match) {
for(var i = 0; i < results.length; i++)
if(results[i].getIncludeURL())
results.splice(i--,1);
return results;
};
(function(){
//==============================================================================
// install only once stuff
// Ensure the global abego namespace is set up.
if (!window.abego) window.abego = {};
// Install only once (don't install if abego.IncludePlugin is installed as well)
if (abego.TiddlyWikiIncluder)
return alert("Warning: abego.TiddlyWikiIncluder already exists, so probably two copies of SharedTiddlersPlugin and/or IncludePlugin are installed and activated. It is highly recommended to deactivate all but one copy. You can find those by searching 'abego.TiddlyWikiIncluder'.");
// abego.TiddlyWikiIncluder is defined near the end of the code
// Define the API namespace:
window.sharedTiddlersAPI = {};
//==============================================================================
// Helpers
// This is used instead of displayMessage, because the latter sometimes doesn't show the messages
var displayAndLogMessage = function(text,linkText) {
displayMessage(text,linkText);
console.log(text);
};
var invokeLater = function(func, delay, priority) {
return setTimeout(func,delay);
};
//------------------------------------------------------------------------------
// url helpers
var isRelativeURL = function(url) {
// as Unix filesystem root is "/", urls starting with it are not considered as relative
return (url.search(/^(?:((http(s)?)|(file)):)|(.\:\\)|(\\\\)|(\/)/) != 0);
};
var getPathFromURL = function(url) {
return (url.lastIndexOf("/") > -1) ?
url.substr(0, url.lastIndexOf("/") + 1) : "";
};
var resolveUrlFrom = function(urlToResolve, sourceUrl) {
return (isRelativeURL(urlToResolve) && sourceUrl) ?
getPathFromURL(sourceUrl) + urlToResolve : urlToResolve;
};
// limitedly turns URI (URL) reference into an absolute URI (URL) and windows paths into URL
var stp_resolveURL = function(url) {
if (url.search(/^((http(s)?)|(file)):/) != 0) {
// no protocol prefix..
if (isRelativeURL(url))
url = resolveUrlFrom(url, document.location.toString());
else
// "url" is an "absolute" path to a local file. Prefix it with file://
url = "file://" + url;
// replace every \ by a /, to cover Windows style pathes
url = url.replace(/\\/mg,"/");
}
return url;
};
//------------------------------------------------------------------------------
// file/tw loading functions
// an evolution of the deprecated loadRemoteFile function with TW 2.7.0 codes
var stp_loadRemoteFile = function(url,callback,params) {
if(version.major < 2 || version.major == 2 && version.minor < 7) {
var httpSuccess = function(xhr) {
try {
return (!xhr.status && location.protocol === "file:") ||
(xhr.status >= 200 && xhr.status < 300) ||
xhr.status === 304 || xhr.status === 1223;
} catch(e) {}
return false;
};
var options = {
type:"GET",
url:url,
processData:false,
data:undefined, // cut off?
cache:false,
beforeSend: function(xhr) {;},
complete: function(xhr,textStatus) {
if(httpSuccess(xhr))
callback(true,params,xhr.responseText,url,xhr);
else
callback(false,params,null,url,xhr);
}
};
try {
if(window.Components && window.netscape && window.netscape.security && document.location.protocol.indexOf("http") == -1)
window.netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
} catch (ex) {}
return jQuery.ajax(options);
} else
return httpReq("GET",url,callback,params);
};
// Asynchronously load the given (local or remote) file.
//
// @param url
// value: either an URL or a local file path to a file
//
// Examples:
// * http://www.abego-software.de/index.html
// * file:///C:/abegoWebSite-Copy/index.html
// * C:\abegoWebSite-Copy\index.html (for Windows machines)
//
// Notice: backslashes in JavaScript string constants must be escaped,
// i.e. the last example must be written as: "C:\\abegoWebSite-Copy\\index.html"
// when "hardcoded" in JavaScript source code.
//
// @param callback
// value: function(content,url,params,errorMessage)
// called at the end of the operation.
// On success content holds the content of the loaded file.
// On error content is undefined and errorMessage holds an error message.
// params is the params passed into stp_LoadFile.
//
// @param params
// passed through to the callback function
//
var stp_LoadFile = function(url,callback,params) {
var onLoad = function(status,params,responseText,url,xhr) {
return status
? callback(responseText, url, params)
: callback(undefined, url, params, "Error loading %0".format([url]));
};
// Make sure the URL is a real URL, with protocol prefix etc.
url = stp_resolveURL(url);
stp_loadRemoteFile(url,onLoad,params);
};
// Asynchronously load the given (local or remote) TiddlyWiki store.
//
// @param url
// value: either an URL or a local file path to a TiddlyWiki file (absolute or relative)
//
// Examples:
// * http://www.abego-software.de/index.html
// * file:///C:/abegoWebSite-Copy/index.html
// * include/beta.html
// * C:\abegoWebSite-Copy\index.html (for Windows machines)
//
// Notice: backslashes in JavaScript string constants must be escaped,
// i.e. the last example must be written as: "C:\\abegoWebSite-Copy\\index.html"
// when "hardcoded" in JavaScript source code.
//
// @param callbackWithStore
// value: function(theStore,url,params,errorMessage)
// called at the end of the operation.
// On success theStore holds the loaded store (a TiddlyWiki object).
// On error theStore is undefined and errorMessage holds an error message.
// params is the params passed into stp_loadTiddlyWikiStore
//
// @param params
// passed through to the callbackWithStore
//
// @param progress [optional]
// value: function(message, sender, state, url, params)
// called in various situations during the operation,
// typically used to show "the progress" of the operation.
// sender: the constant "stp_loadTiddlyWikiStore"
// state: one of these: "Started", "Processing", "Done", "Failed"
// "Processing" means the data has been received and in now processed.
//
var stp_loadTiddlyWikiStore = function(url,callbackWithStore,params,progress) {
var sendProgress = function(message, state) {
if (progress)
progress(message,"stp_loadTiddlyWikiStore",state,url,params);
};
var sendError = function(message) {
sendProgress("Error when loading %0".format([url]),"Failed");
callbackWithStore(undefined, url, params, message);
return message;
};
var sendStore = function(store) {
sendProgress("Loaded %0".format([url]),"Done");
callbackWithStore(store, url, params);
return null;
};
var callback = function(content,theURL,params,errorMessage) {
if (content === undefined) {
sendError(errorMessage);
return;
};
sendProgress("Processing %0".format([url]),"Processing");
var invalidFileErrorMsg = "The file '%0' does not appear to be a valid TiddlyWiki file";
try {
// Load the content from the "content" string into a TiddlyWiki() object
var importStore = new TiddlyWiki();
var errorText = importStore.importTiddlyWiki(content)? null :
"Problem with importing TiddlyWiki, probable reason is: "+
invalidFileErrorMsg.format([url]);
if(errorText)
sendError(errorText);
else
sendStore(importStore);
} catch (ex) {
sendError(exceptionText(ex));
};
};
sendProgress("Start loading %0".format([url]),"Started");
stp_LoadFile(url,callback,params);
};
//------------------------------------------------------------------------------
// plugin installation helpers
var getArbitraryPluginInfo = function(tiddler) {
// getPluginInfo can't be used (for included tiddlers) because of the ugly slice handling of the core
// (it uses {store} where it could handle the tiddler directly)
var pInfo = {};
var slices = {};
var requiredInfo = ["Name","Description","Version","Requires","CoreVersion","Date","Source", "Author","License","Browsers"];
store.slicesRE.lastIndex = 0;
var match = store.slicesRE.exec(tiddler.text);
while(match) {
if(match[2])
slices[match[2]] = match[3];
else
slices[match[5]] = match[6];
match = store.slicesRE.exec(tiddler.text);
}
for(var i = 0; i < requiredInfo.length; i++)
if(slices[requiredInfo[i]])
pInfo[requiredInfo[i]] = slices[requiredInfo[i]];
pInfo.tiddler = tiddler;
pInfo.title = tiddler.title;
pInfo.log = [];
return pInfo;
}
var checkPluginInstalled = function(pluginName) {
for(var i = 0; i < installedPlugins.length; i++)
if(installedPlugins[i].title == pluginName || installedPlugins[i].Name == pluginName)
return true;
return false;
}
var installPlugin = function(tiddler,force) {
var pluginName = getArbitraryPluginInfo(tiddler).Name || tiddler.title;
// check if such a plugin was installed previously, return if so
if(!force) // two layers to improve the speed in the "force == true" case
if(checkPluginInstalled(pluginName))
return;
// get the plugin info
var pluginInfo = getArbitraryPluginInfo(tiddler);
if(tiddler.getIncludeURL())
pluginInfo.log.push("included from "+tiddler.getIncludeURL());
// install the plugin
pluginInfo.executed = true;
var startTime = new Date();
try {
window.eval(tiddler.text);
} catch(ex) {
pluginInfo.log.push(config.messages.pluginError.format([exceptionText(ex)]));
pluginInfo.error = true;
console.log("error evaluating " + tiddler.title, ex);
story.displayTiddler(null,"PluginManager");
displayMessage(config.messages.customConfigError);
}
pluginInfo.startupTime = String((new Date()) - startTime) + "ms";
// register the plugin
installedPlugins.push(pluginInfo);
}
//==============================================================================
// Shared Tiddlers Plugin
// Constants
var WAITING = "waiting";
var LOADING = "loading";
var ANI_DURATION_HIDE_STATE = 1000;
var REFRESH_PRIORITY = -200;
var ANIMATION_PRIORITY = -100;
var UPDATE_STATE_PRIORITY = -300;
// --------------------------------------------------
// Variables
var useInclude; // this variable can be used to control include enabling by other things than cookies
var includedStores = {}; // url(String) -> TiddlyWiki or String; if not (yet) loaded a status or an error string
var pendingOnLoadURLs = []; // [] of String: a list of urls that should be passed with the next "notifyListeners"
var refreshTiddlyWikiTimerID; // for delayed refresh
var listeners = [];
var progress;
// rules pointing which stores tiddlers with conflicting names should be fetched from
function FetchPreferences() {
var prefs = {};
this.add = function(tiddlerName,sourceUrl,substitute,substituteShadow) {
if(!substitute && !substituteShadow)
return;
if(prefs[tiddlerName] == undefined)
prefs[tiddlerName] = {urlsSubs: [], urlsSubsSh: []};
if(substitute)
prefs[tiddlerName].urlsSubs.pushUnique(sourceUrl);
if(substituteShadow)
prefs[tiddlerName].urlsSubsSh.pushUnique(sourceUrl);
};
/* this.getPrefs = function(tiddlerName) {
return jQuery.extend(true, {}, prefs[tiddlerName]);
};
*/ this.getSubsUrl = function(tiddlerName) {
var pref = prefs[tiddlerName];
if(pref == null)
return null;
if(pref.urlsSubs.length == 0)
return null;
return pref.urlsSubs[0];
};
this.getSubsShUrl = function(tiddlerName) {
var pref = prefs[tiddlerName];
if(pref == null)
return null;
if(pref.urlsSubsSh.length == 0)
return null;
// check if there's a tiddler with both preferences
var i, j;
for(i = 0; i < pref.urlsSubs.length; i++)
for(j = 0; i < pref.urlsSubsSh.length; j++)
if(pref.urlsSubs[i] == pref.urlsSubsSh[j])
return pref.urlsSubs[i];
return pref.urlsSubsSh[0];
};
this.containSubsUrl = function(tName,url) {
var pref = prefs[tName];
if(pref == null)
return false;
return pref.urlsSubs.contains(url);
};
this.containSubsShUrl = function(tName,url) {
var pref = prefs[tName];
if(pref == null)
return false;
return pref.urlsSubsSh.contains(url);
};
this.removeByUrl = function(url) { // removes all priorities of tiddlers, included from the url
var tName, pref, i;
for(tName in prefs) {
pref = prefs[tName]
for(i = 0; i < pref.urlsSubs.length; i++)
if(pref.urlsSubs[i] == url)
pref.urlsSubs.splice(i--,1);
for(i = 0; i < pref.urlsSubsSh.length; i++)
if(pref.urlsSubsSh[i] == url)
pref.urlsSubsSh.splice(i--,1);
// if (pref.urlsSubs.length == 0 && pref.urlsSubsSh.length == 0), not nec. to delete pref
}
};
};
var fetchPreferences = new FetchPreferences();
function Conflicts() {
// hashmaps by tiddler name of potentially conflicting tiddlers with that name;
// each element is a hashmap by url of tiddlers with such names
var pConfs = {}, // doesn't contain info about tiddlers in the main store
pSConfs = {}; // for tiddlers that may conflict when substituting a shadow
this.init = function() {
// notify of name conflicts in the whole set of initial and included tiddlers?
if(config.options.chkWarnOnSharedTiddlersConflicts == undefined) config.options.chkWarnOnSharedTiddlersConflicts = true;
// use alert() for notifications?
if(config.options.chkAlertOnSharedTiddlersConflicts == undefined) config.options.chkAlertOnSharedTiddlersConflicts = false;
};
// check whether a new tiddler (not included yet) may cause a conflict (now or in the future)
this.checkNew = function(tName, url, subs, subsSh) {
// use to add when a tiddler with such name is already present
var addData = function(confsMap, tidParams) {
var includeData = function(tidParams) { // better to create a separate class
return { subs: tidParams.substitute, warned: false };
};
var getSubsPriority = function(data) {
return data.subs;
}
var conf = confsMap[tidParams.title];
if(!conf) {
conf = confsMap[tidParams.title] = {};
conf[tidParams.url] = includeData(tidParams);
} else {
var confPart = conf[tidParams.url];
if(!confPart)
conf[tidParams.url] = includeData(tidParams);
else {
if(!getSubsPriority(confPart) && tidParams.substitute)
conf[tidParams.url] = includeData(tidParams);
// better to set subs to true and warned to false
}
}
}
// check if there's a tiddler with such a name
var existingTid = forEachLoadedStore(function(theStore, storeUrl) { // in included stores
if(storeUrl != url)
return theStore.fetchTiddler(tName);
}) || window.sharedTiddlersAPI.orig_fetchTiddler(tName); // or in the main one
// if the tiddler is added to the main store after exactly one tiddler is included, no conflict is
// detected by this algorithm; also, it doesn't account deleting the tiddler from the main store
if(!existingTid)
return;
var mainStoreId = "main store",
existingTidUrl = existingTid.getIncludeURL();
// check conflicts among all included tiddlers (important when no shadow with such name exists)
if(!pConfs[tName])
addData(pConfs, {
title: tName,
url: existingTidUrl || mainStoreId,
substitute: existingTidUrl ? fetchPreferences.containSubsUrl(tName,existingTidUrl) : undefined
});
addData(pConfs, { title: tName, url: url, substitute: subs });
// check conflicts among tiddlers that substitute a shadow
if(subsSh) {
var existingTidSubsSh = forEachLoadedStore(function(theStore, storeUrl) {
if(storeUrl != url && fetchPreferences.containSubsShUrl(tName,storeUrl))
return theStore.fetchTiddler(tName);
}) || window.sharedTiddlersAPI.orig_fetchTiddler(tName);
// same problems
if(!existingTidSubsSh)
return;
var existingTidSubsShUrl = existingTidSubsSh.getIncludeURL();
// conflicts among substituting tiddlers are important when a shadow with such name exists
if(!pSConfs[tName])
addData(pSConfs, {
title: tName,
url: existingTidSubsShUrl || mainStoreId,
substitute: existingTidSubsShUrl ? fetchPreferences.containSubsShUrl(tName,existingTidSubsShUrl) : undefined
});
addData(pSConfs, { title: tName, url: url, substitute: subs });
}
};
this.markNodeUnloaded = function(url) {
var t;
for(t in pConfs)
if(pConfs[t][url])
delete pConfs[t][url];
for(t in pSConfs)
if(pSConfs[t][url])
delete pSConfs[t][url];
};
this.notify = function() {
var msgAndLog = config.options.chkWarnOnSharedTiddlersConflicts;
var doAlert = config.options.chkAlertOnSharedTiddlersConflicts;
if(!doAlert && !msgAndLog)
return; // no conflict is marked as "warned" - this is by intent
var tName, tUrl;
var checkOrDisplayConflicts = function(map,msgAndLog,markWarned) {
var msg = ""; // message to return (for alerting etc)
var addM = function(m) { // pushes all notification messages
msg += ("\n"+m);
if(msgAndLog)
displayAndLogMessage(m);
};
var tName, tSources, tUrl, subs, nOfConflicting, newPresent,
subsMsg = " (with the subsitute priority)";
// mark conflicts among tiddlers with the substitute priority
// find out where necessary, notify
for(tName in map) {
tSources = map[tName];
// find out which priority is of interest
subs = false;
for(tUrl in tSources)
if(tSources[tUrl].subs)
subs = true;
// if there's only one tiddler with "substitute", there's no conflict
nOfConflicting = 0;
for(tUrl in tSources)
if(tSources[tUrl].subs == subs)
nOfConflicting++;
if(nOfConflicting < 2)
break;
// find out if new conflicting tiddlers are present
newPresent = false;
for(tUrl in tSources)
if(tSources[tUrl].subs == subs && !tSources[tUrl].warned)
newPresent = true;
// start notification
if(newPresent) {
addM("* "+tName+" in:");
for(tUrl in tSources)
if(tSources[tUrl].subs == subs && !tSources[tUrl].warned) {
addM("** "+tUrl);
if(markWarned)
tSources[tUrl].warned = true;
}
if(subs)
addM(subsMsg);
}
}
return msg;
};
var msg = "";
if(checkOrDisplayConflicts(pConfs,false,false)) { // new conflicts present
msg += "New conflicts:";
if(msgAndLog) displayAndLogMessage(msg);
msg += checkOrDisplayConflicts(pConfs,msgAndLog,true);
};
if(checkOrDisplayConflicts(pSConfs,false,false)) { // new conflicts among tiddlers substituting shadows present
if(msg) msg += "\n";
var m = "New conflicts among tiddlers competing for substituting shadows:";
msg += m;
if(msgAndLog) displayAndLogMessage(m);
msg += checkOrDisplayConflicts(pSConfs,msgAndLog,true);
};
if(doAlert && msg)
alert(msg);
};
// this.state = // return current state as a string (for includeState)
};
conflicts = new Conflicts();
conflicts.init();
// --------------------------------------------------
// Helper functions
var isIncludeEnabled = function() {
if (useInclude === undefined)
useInclude = config.options.chkUseInclude === undefined || config.options.chkUseInclude;
return useInclude;
};
var getMissingIncludeMsg = function(url) {
return "No include specified for %0".format([url])
};
// Called after one or more included TiddlyWikis are loaded
//
var notifyListeners = function() {
var urls = pendingOnLoadURLs;
pendingOnLoadURLs = [];
if (urls.length)
for (var i = 0; i < listeners.length; i++)
listeners[i](urls);
};
var idleCount; // Reset to 0 when the system is "not idle", incremented inside refreshTiddlyWiki
var refreshTiddlyWiki = function() {
// To avoid to much refreshing/flickering don't refresh immediately
// but wait until the system was idle for a certain time.
if (refreshTiddlyWikiTimerID !== undefined) clearInterval(refreshTiddlyWikiTimerID);
idleCount = 0;
var sendDone = function() {
twWeb.sendProgress("","","Done");
};
refreshTiddlyWikiTimerID = setInterval(function() {
idleCount++;
if (idleCount <= 10)
return;
clearInterval(refreshTiddlyWikiTimerID);
refreshTiddlyWikiTimerID = undefined;
twWeb.sendProgress("Refreshing...","","");
refreshDisplay();
invokeLater(sendDone,0,REFRESH_PRIORITY);
},1);
};
// Calls callback for every loaded store and returns the first non-false/null.. value returned by callback.
//
// @param callback
// value: function(store, url)
//
var forEachLoadedStore = function(callback) {
var result;
for(var url in includedStores) {
var theStore = twWeb.getStore(url);
if (theStore && (result = callback(theStore, url)))
return result;
}
};
// hijack fetchTiddler so that it works with tiddlers from included stores as well
var attachToStore = function() {
if (!window.store)
return invokeLater(attachToStore,100);
var orig_fetchTiddler = store.fetchTiddler;
window.sharedTiddlersAPI.orig_fetchTiddler = orig_fetchTiddler;
// reserve access to the original method to be able to fetch tiddlers from main store,
// including substituted ones
store.fetchTiddler = function(title) {
var t, subsUrl = fetchPreferences.getSubsUrl(title), subsShUrl;
// first, look for the tiddler in the main store, unless there's nothing to substitute with
if(!subsUrl) {
t = orig_fetchTiddler.apply(this,arguments);
if(t) return t;
} else
return includedStores[subsUrl].fetchTiddler(title);
// then, look for shadowed tiddlers in main store and external ones to substitute those
if(config.shadowTiddlers[title] !== undefined) {
if(subsShUrl = fetchPreferences.getSubsShUrl(title))
return includedStores[subsShUrl].fetchTiddler(title);
else
return undefined;
};
// Don't look for the "New Tiddler" tiddler in the included TiddlyWikis,
// since returning such a tiddler (that is readonly) will make it impossible
// in the Main TiddlyWiki to create new tiddlers via standart "new tiddler" button.
if (title == config.macros.newTiddler.title) return undefined;
// finally, look for an external one without "substitute" preference
return forEachLoadedStore(function(theStore, url) {
return theStore.fetchTiddler(title);
});
};
// refresh TiddlyWiki to reflect the new included Tiddlers (if we have any).
if(twWeb.getIncludedStoresUrls().length)
refreshTiddlyWiki();
};
var includeFromIncludeList = function() {
if (!window.store)
return invokeLater(includeFromIncludeList,100);
var includeListText = store.getTiddlerText("IncludeList");
if (includeListText)
wikify(includeListText,document.createElement("div"),undefined,store.fetchTiddler("IncludeList"));
};
var getFunctionUsingForReallyEachTiddler = function(func) {
var wrapper = function() {
var orig_forEachTiddler = store.forEachTiddler;
var forEachTiddlerWithIncludes = function(callback) {
var done = {};
var callbackWrapper = function(title, tiddler) {
// ensure every title is only processed once
if(done[title])
return;
// do and set done for appropriate tiddlers
if(!fetchPreferences.getSubsUrl(title)||
(fetchPreferences.getSubsUrl(title) == tiddler.getIncludeURL())) {
done[title] = 1;
callback.apply(this,arguments);
};
};
// first, forEachTiddler over the original tiddlers
orig_forEachTiddler.call(store, callbackWrapper);
// add nonsubstituted shadowTiddler titles to done
// (to avoid an included store hide a shadow tiddler)
for (var n in config.shadowTiddlers)
if(!fetchPreferences.getSubsShUrl(n))
done[n] = 1;
// add the "New Tiddler" tiddler to done
// to avoid an included store (with such tiddler) prevent creating new tiddlers
done[config.macros.newTiddler.title] = 1;
// forEachTiddler over every included store
forEachLoadedStore(function(theStore, url) {
theStore.forEachTiddler(callbackWrapper);
});
};
store.forEachTiddler = forEachTiddlerWithIncludes;
try {
return func.apply(this,arguments);
} finally {
store.forEachTiddler = orig_forEachTiddler;
};
};
return wrapper;
};
var useForReallyEachTiddler = function(object,property) {
return object[property] = getFunctionUsingForReallyEachTiddler(object[property]);
};
//================================================================================
// config.extensions.SharedTiddlersPlugin (the "includer" engine)
config.extensions.SharedTiddlersPlugin = {
// function config.extensions.SharedTiddlersPlugin.getFunctionUsingForReallyEachTiddler(func)
//
// Returns a function that behaves as func, but every call to store.forEachTiddler will actually
// be a call to forReallyEachTiddler (see below), i.e. iterate over the tiddlers of the main store
// and of the included TiddlyWikis
//
// @return the patched function
//
getFunctionUsingForReallyEachTiddler: getFunctionUsingForReallyEachTiddler,
// function config.extensions.SharedTiddlersPlugin.useForReallyEachTiddler(object,property)
//
// Patches the function hold in the given property of the object in such a way that every call
// to store.forEachTiddler will actually be a call to forReallyEachTiddler (see below), i.e.
// iterate over the tiddlers of the main store and of the included TiddlyWikis
//
// @param object
// @param property the name of the property of the object containing the function to be patched.
// @return the patched function
//
useForReallyEachTiddler: useForReallyEachTiddler,
// Add a listener function to the TiddlyWikiIncluder.
//
// @param listener function(urls)
// urls: [] of Strings, containing the urls of the TiddlyWiki just included
// (see url@config.extensions.SharedTiddlersPlugin.include)
// called whenever one or more TiddlyWiki store are successfully included.
//
addListener: function(listener) {
listeners.push(listener);
}
};
// -------------------------------------------------------------------------------
// TiddlyWikiIncluder initialization code
config.extensions.SharedTiddlersPlugin.addListener(refreshTiddlyWiki);
config.shadowTiddlers.AdvancedOptions +=
("\n~IncludePlugin settings:"+
"\n<<option chkUseInclude>> Include ~TiddlyWikis"+
"\n<<option chkAlertOnSharedTiddlersConflicts>> Alert on tiddler name conflicts"+
"\n<<option chkWarnOnSharedTiddlersConflicts>> Display messages and write log in the browser console on conflicts"+
"\nIncludeList | IncludeState | ImportIncluded | [[help|http://yakovl.bplaced.net/TW/SharedTiddlersPlugin.html/#SharedTiddlersPluginInfo]]"+
"\n^^(Reload this ~TiddlyWiki to make changes become effective)^^");
config.shadowTiddlers.IncludeState = "<<includeState>>";
// add the "importer" engine
config.shadowTiddlers.ImportIncluded =
"| the url of the document to import from | <<option txtStoreUrl>> |\n"+
"| the filter of tiddlers to import | <<option txtFilterTiddlersToImport>> |\n"+
"| the importing mode | <<option txtImportMode>> |\n"+
"<html><a href='javascript:;' onclick='\n"+
" var storeUrl = config.options.txtStoreUrl,\n"+
" params = {\n"+
" filterLine: config.options.txtFilterTiddlersToImport,\n"+
" importMode: config.options.txtImportMode,\n"+
" noRefresh: true,\n"+
" };\n"+
" if(!storeUrl)\n"+
" return displayMessage(\"please specify the url to import from\");\n"+
" if(!params.filterLine)\n"+
" return displayMessage(\"please specify the filter of tiddlers to import\");\n"+
" if(!sharedTiddlersAPI.getStore(storeUrl))\n"+
" displayMessage(\"warning: no store was loaded from \"+storeUrl+\" previously, trying now\");\n"+
" twWeb.include(storeUrl,params);\n"+
"'>import (without saving)<a/></html>";
//================================================================================
// Tiddler extension/modification
Tiddler.prototype.isIncluded = function() {
return this.includeURL != undefined;
};
Tiddler.prototype.getIncludeURL = function() {
return this.includeURL;
};
Tiddler.prototype.setIncludeURL = function(url) {
this.includeURL = url;
};
Tiddler.prototype.deleteIncludeURL = function() {
delete this.includeURL;
};
// make included tiddlers readonly
config.extensions.SharedTiddlersPlugin.orig_Tiddler_isReadOnly = Tiddler.prototype.isReadOnly;
Tiddler.prototype.isReadOnly = function() {
return config.extensions.SharedTiddlersPlugin.orig_Tiddler_isReadOnly.apply(this,arguments) || this.isIncluded();
}
//================================================================================
// TiddlyWiki modifications
// In some TiddlyWiki functions the "forEachTiddler" should work on all tiddlers, also those from
// included store. (E.g. TiddlyWiki.prototype.getTags)
//
// But not for all (e.g. TiddlyWiki.prototype.getTiddlers is used for saving, but only the "own" tiddlers should be saved)
//
// Therefore explicitly list the functions that should be "wrapped" to use the "forReallyEachTiddler".
//
var tiddlyWikiFunctionsUsingForReallyEachTiddler = {
getMissingLinks: 1, getOrphans: 1,
getTags: 1, reverseLookup: 1, updateTiddlers: 1};
for (var n in tiddlyWikiFunctionsUsingForReallyEachTiddler)
useForReallyEachTiddler(TiddlyWiki.prototype,n);
//================================================================================
// Web of TiddlyWikis
function IncludingMemorizer() { // the structure to store info about already handled include macros
var used = {};
this.isUsed = function(line) { return used[line]? true : false; };
this.setUsed = function(line) { used[line] = 1; };
this.getUsed = function() { return jQuery.extend(true, {}, used) };
this.markUnused = function(usedMap) {
for(var u in usedMap)
used[u] = undefined; // don't delete to keep the order of inclusion (for reloading)
};
};
function TwWeb() {
var nodes = {}; // hashmap by node name of nodes' metadata
var nodeConflicts = {}; // hashmap by node name of arrays of conflicts
var nodeWaitingTasks = {}; // hashmap by node name of arrays of Waiting Tasks
var nodeDescription = function(url) { // PoG: can be turned into a separated "class"
return { url : url };
};
var selfNodeName;
var self = this;
this.includeUsages = new IncludingMemorizer();
// ----- Nodes desctiptions part ---------------------------------------------------------------------
// set/get the name of "main" (including) node
this.setSelfNodeName = function(name) {
selfNodeName = name;
};
this.getSelfNodeName = function() {
return selfNodeName;
};
// set/get/delete node description
this.setNodeDesc = function(nodeName, desc) {
var existing = this.getNodeDesc(nodeName);
// currently, doesn't change metadata on conflict
if(existing)
this.addConflict(nodeName, desc);
else
nodes[nodeName] = desc;
this.callWaitingTasks(nodeName);
};
this.getNodeDesc = function(nodeName) {
return nodes[nodeName];
};
this.deleteNodeDesc = function(nodeName) {
nodes[nodeName] = null;
};
// "API" method to be used in the macro
this.addNodeDesc = function(nodeName, url) {
this.setNodeDesc(nodeName, nodeDescription(url));
};
// returns "better" description if one is "strictly better" than the other or "even" and null otherwise
this.compareNodeDesc = function(desc1, desc2) {
// looks like here's a mistake: probably ": desc2" (?)
return (desc1.url == desc2.url)? desc1 : null;
};
this.getNodeUrl = function(nodeName) {
var desc = this.getNodeDesc(nodeName);
return desc? desc.url : null;
};
// @param nodeId
// url or "node: nodeName" id of the node
// @return url of the node (if it is defined) or null
//
this.getStoreUrlById = function(nodeId) {
var node = self.checkNodeNotation(nodeId),
url = node ? self.getNodeUrl(node) : nodeId;
return url;
}
this.setNodeNotation = function(nodeName) {
return "node: " + nodeName;
};
this.checkNodeNotation = function(urlParam) {
var nodeCalcRE = /node: (.*)/,
nodeCalcMatch = nodeCalcRE.exec(urlParam);
return nodeCalcMatch ? nodeCalcMatch[1] : null;
};
this.addConflict = function(nodeName, nodeDesciption) {
var betterDesc = this.compareNodeDesc(nodeDesciption,this.getNodeDesc(nodeName));
if(betterDesc) {
this.deleteNodeDesc(nodeName);
this.setNodeDesc(nodeName, betterDesc);
return;
}
if(nodeConflicts[nodeName])
nodeConflicts[nodeName].push(nodeDesciption);
else
nodeConflicts[nodeName] = [ nodeDesciption ];
alert( "Warning: more than one description of the "+nodeName+" node was pushed. "+
"The earlier version is kept." );
};
// ----- Waiting tasks part --------------------------------------------------------------------------
this.setWaitingTask = function(nodeName, waitingTaskFunc, waitingTaskSelf) {
var waitingTask = { action: waitingTaskFunc, self: waitingTaskSelf };
if(nodeWaitingTasks[nodeName])
nodeWaitingTasks[nodeName].push(waitingTask);
else
nodeWaitingTasks[nodeName] = [ waitingTask ];
if(this.getNodeUrl(nodeName))
this.callWaitingTasks(nodeName);
};
this.callWaitingTasks = function(nodeName) {
var toDo = nodeWaitingTasks[nodeName];
if(toDo)
for(var i = 0; i < toDo.length; i++)
toDo[i].action.call(toDo[i].self);
nodeWaitingTasks[nodeName] = null; // remove called Waiting Tasks
};
// ----- Including stuff part ------------------------------------------------------------------------
// ---- helpers ----
this.setProgressFunction = function(func) {
progress = func;
};
/* this.getProgressFunction = function() { // is not in use
return progress;
};
*/ this.sendProgress = function(message, sender, state) {
if (progress)
progress.apply(this,arguments);
};
// Returns true when there are "pending" includes, i.e. TiddlyWiki that are not yet loaded.
// A TiddlyWiki that failed loading is not pending.
//
this.hasPendingIncludes = function() {
var state;
for(var url in includedStores) {
state = this.getState(url);
if (state == WAITING || state == LOADING)
return true;
};
return false;
};
// Called when an included TiddlyWiki could not be loaded.
// By default an error message is displayed.
//
this.onError = function(url, errorMessage) {
displayAndLogMessage("Error when including '%0':\n%1".format([url, errorMessage]));
};
// import a tiddler from an included store
//
// @param tiddler - a tiddler to import
// @param mode: undefined or 1 - import anyway (other modes, like "don't substitute" will be supported)
// @return the result object:
// .status means: -1 = error, 0 = tiddler is imported, >0 - reserved for other situations
// .errorText is not empty on error
// .tiddler is the tiddler with /the title/ which is in the store after "it is over" (may be null)
// .from is the url line equal to the includeURL of the imported tiddler
//
this.importIncluded = function(tiddler,mode) {
if(mode == undefined)
mode = 1;
else
mode = parseInt(mode);
var t, tInMain, result = { tiddler: null };
var doImport = function(t) {
store.addTiddler(t);
store.setDirty(true);
result.status = 0;
result.tiddler = t;
result.from = url;
};
if(tiddler instanceof Tiddler) {
// see return value, this is for the case when the import is not done
result.tiddler = window.sharedTiddlersAPI.orig_fetchTiddler(tiddler.title);
t = jQuery.extend(true, new Tiddler(), tiddler);
} else {
result.status = -1;
result.errorText = "not a Tiddler instance";
return result;
};
var url;
if(url = t.getIncludeURL())
t.deleteIncludeURL();
switch(mode) {
case 4: { // import only newer and on confirm
tInMain = window.sharedTiddlersAPI.orig_fetchTiddler(t.title);
if(!tInMain || tInMain.modified < t.modified)
if(confirm("Up-to-date "+t.title+" from "+url+" is availabe, import?"))
doImport(t);
break
}
case 2: { // import only newer versions/unexisting tiddlers
tInMain = window.sharedTiddlersAPI.orig_fetchTiddler(t.title);
if(!tInMain || tInMain.modified < t.modified)
doImport(t);
break
}
case 3: { // import on confirm
if(confirm(t.title+" from "+url+" is availabe, import?"))
doImport(t);
break
}
case 1: { // import anyway
doImport(t);
break
}
default: {
result.status = -1;
result.errorText = "unknown import mode";
}
};
return result;
};
this.importAndLog = function(tiddler,mode) {
var name = tiddler.title,
result = twWeb.importIncluded(tiddler,mode);
// this.importIncluded is not used to be able to pass the method to the API
switch(result.status) {
case 0: console.log("imported: "+name+" from "+result.from);
break
case -1: console.log("error importing "+name+": "+result.errorText);
break
}
};
// ---- main ----
// Includes the (local or remote) TiddlyWiki store with the given url.
//
// @param url see url@stp_loadTiddlyWikiStore
// @param includeParams a set of the following params for including:
// filterLine a filter expression defining a set of tiddlers to include
// substituting points whether an included tiddler should sustitute
// one in the main document if there's a conflict of names
// substituteShadows points whether shadowed tiddlers of the main document with names equal to
// those of included ones should be substituted (in cases of conflicts);
// works only with (substituting == true)
// delayMilliSeconds addition delay of loading
// noRefresh
// importMode
// evalTiddlers
// wikifyTiddlers
//
this.include = function(urlOrNodeParam, includeParams) {
if (!isIncludeEnabled())
return;
var self = this;
var includeOrHandleUrl = function(url) {
var loadStoreCallback = function(theStore,urlInCallback,params,errorMessage) {
if(theStore === undefined) {
includedStores[url] = errorMessage;
self.onError(url, errorMessage);
return;
} else
includedStores[url] = theStore;
// keep orig_store not to load tw-documents multiple times
// because of multiple include macros:
includedStores[url].orig_store = new TiddlyWiki();
includedStores[url].forEachTiddler(function(tName,tiddler){
tiddler.setIncludeURL(url);
includedStores[url].orig_store.addTiddler(tiddler);
});
includedStores[url].clear();
// include, create fetchPreferences and notify of conflicts; or import
addFromLoadedStore();
};
var addFromLoadedStore = function() {
// uses url and includeParams exploiting closure
var substituting = includeParams.substitute,
substituteShadows = includeParams.substituteShadows,
importMode = includeParams.importMode;
if(twWeb.getStore(url) === null)
return invokeLater(addFromLoadedStore, 100); // 100 milliseconds
// new tiddlers should be added *when the store is loaded*
// add tiddlers to included stores and check new conflicts (among non-subs. tids)
var i, t,
new_tiddlers = includedStores[url].orig_store.filterTiddlers(includeParams.filterLine);
for(i = 0; i < new_tiddlers.length; i++) {
t = jQuery.extend(true, new Tiddler(), new_tiddlers[i]); //copy (by value)
if(includedStores[url].orig_store.fetchTiddler(t.title)) {
// ignore empty tiddlers created by the "tiddler" filter
if(importMode)
twWeb.importAndLog(t,importMode);
else {
// check for upcoming conflicts first
conflicts.checkNew(t.title, url, substituting, substituteShadows);
// then include
includedStores[url].addTiddler(t);
};
if(includeParams.evalTiddlers)
installPlugin(t);
if(includeParams.wikifyTiddlers)
wikify(t.text,document.createElement("div"),undefined,t);
};
};
// add items to fetchPreferences if have to
if(!importMode && (substituting || substituteShadows))
for(i = 0; i < new_tiddlers.length; i++)
fetchPreferences.add(new_tiddlers[i].title,url,substituting, substituteShadows);
conflicts.notify();
// "recalc" slices
store.slices = {};
// refresh things to get included stylesheets, PageTemplate and ViewTemplate applied
if(!includeParams.noRefresh) {
refreshAll();
story.refreshAllTiddlers();
}
pendingOnLoadURLs.push(url);
invokeLater(notifyListeners);
};
var loadStore = function() {
includedStores[url] = LOADING;
stp_loadTiddlyWikiStore(url,loadStoreCallback,null,progress);
// {includeParams:includeParams} can be used instead of null so that
// loadStoreCallback will have access to the includeParams
};
var urlIsNew = !(includedStores[url]);
if(urlIsNew) {
includedStores[url] = WAITING;
if (includeParams.delayMilliSeconds)
invokeLater(loadStore, includeParams.delayMilliSeconds);
else
loadStore();
} else
addFromLoadedStore();
};
var nodeName = this.checkNodeNotation(urlOrNodeParam);
if(nodeName) {
if(nodeName == this.getSelfNodeName()) // don't include from the main (self) TiddlyWiki
return;
this.setWaitingTask(nodeName,function(){
var url = this.getNodeUrl(nodeName);
includeOrHandleUrl(url);
},this);
} else
includeOrHandleUrl(urlOrNodeParam);
};
// ----- Methods for dealing with included stuff -----------------------------------------------------
// @return [] of Strings, the URLs of the includes
//
this.getIncludedStoresUrls = function() { // in a form of an array
var includes = [];
for(var url in includedStores)
includes.push(url);
return includes;
};
// @return the (TiddlyWiki) store with the given URL or "node: nodeName", or null if not (yet) loaded.
//
this.getStore = function(nodeId) {
var url = self.getStoreUrlById(nodeId)
if(!url)
return null;
var s = includedStores[url];
if(s && s instanceof TiddlyWiki)
return s;
return null;
};
// @return a state/error text of the store with the given URL, or null when the store is already loaded
//
this.getState = function(nodeId) {
var url = self.getStoreUrlById(nodeId)
if(!url)
return "the node "+self.checkNodeNotation(nodeId)+" is not described yet, the address is unknown";
var s = includedStores[url];
if (!s)
return getMissingIncludeMsg(url);
return typeof s == "string" ? s : null;
};
// reload one included store or all of them
// previous inclusions are done again, by default only those without eval and import
// important: reload doesn't work correctly with <<include>> usages with multiple urls
//
// @param reloadParams an object containing some of these configuration properties:
// urlOrNodeList an array of TWs' IDs (url or "node: nodeName") to reload;
// if undefined, all TWs are reloaded
// eval points whether to redo inclusions with the "eval" parameter (undefined == false)
// import same for the "import" parameter, but undefined/null -> true
// wikify same for the "wikify" parameter
this.reload = function(reloadParams) {
// determine a TW(s) to reload (undefined => all)
var i, twsToReload = reloadParams.urlOrNodeParam;
// turn "IDs" into actual urls; filter out node names for which urls are not defined
// so waiting tasks are not doubled
if(twsToReload)
for(i = 0; i < twsToReload.length; i++){
nodeName = this.checkNodeNotation(twsToReload[i]);
twsToReload[i] = nodeName ? this.getNodeUrl(nodeName) : twsToReload[i];
if(!twsToReload[i])
twsToReload.splice(i--,1);
}
if(reloadParams.import === undefined || reloadParams.import === null)
reloadParams.import = true;
if(reloadParams.wikify === undefined || reloadParams.wikify === null)
reloadParams.wikify = true;
// collect macro expressions to reload
// may the order of including be important?
var usage, usages = this.includeUsages.getUsed(),
paramString, params, pParams, urlParam, nodeName,
dontReload, i;
for(usage in usages) {
// parsing copied from config.macros.include.handler (to get macro params)
paramString = usage.substring(10,usage.length - 2);
params = paramString.readMacroParams();
pParams = paramString.parseParams("url",null,true,false,true);
urlParam = pParams[0]["url"][0];
nodeName = this.checkNodeNotation(urlParam);
urlParam = nodeName ? this.getNodeUrl(nodeName) : urlParam;
// keep only inclusions that contain IDs corresponding to urls from twsToReload
dontReload = true;
if(twsToReload) {
for(i = 0; i < twsToReload.length; i++)
if(urlParam == twsToReload[i])
dontReload = false;
} else
dontReload = false;
// exclude inclusions with import and eval, if necessary
if(!reloadParams.import && getParam(pParams,"import",undefined) ||
!reloadParams.eval && params.contains('eval') ||
!reloadParams.wikify && params.contains('wikify'))
dontReload = true;
if(dontReload) {
delete usages[usage];
continue;
}
// clean stuff: unload included store, clear priorities, remove conflicts;
// deleting "used" marks is outside this loop
includedStores[urlParam] = undefined;
fetchPreferences.removeByUrl(urlParam);
conflicts.markNodeUnloaded(urlParam);
}
// delete "used" marks
this.includeUsages.markUnused(usages);
// include again
for(usage in usages)
wikify(usage,document.createElement("div"),undefined,null);
};
};
twWeb = new TwWeb();
//================================================================================
// Default Progress Handling for config.extensions.SharedTiddlersPlugin
var showAnimated = function(e, showing, duration) {
// if (!anim || !abego.ShowAnimation) {
e.style.display = showing ? "block" : "none";
return;
// }
// anim.startAnimating(new abego.ShowAnimation(e,showing,duration));
};
config.extensions.SharedTiddlersPlugin.getDefaultProgressFunction = function() {
setStylesheet(
".includeProgressState{\n"+
"background-color:#FFCC00;\n"+
"position:absolute;\n"+
"right:0.2em;\n"+
"top:0.2em;\n"+
"width:7em;\n"+
"padding-left:0.2em;\n"+
"padding-right:0.2em\n"+
"}\n",
"stp_Include");
var createStateElem = function() {
var e = document.createElement("div");
e.className = "includeProgressState";
e.style.display = "none";
document.body.appendChild(e);
return e;
};
var stateElem = createStateElem();
var showState = function(message) {
removeChildren(stateElem);
createTiddlyText(stateElem,message);
showAnimated(stateElem,true,0);
};
var hideState = function() {
// hide the state the next idle time
invokeLater(function() {
showAnimated(stateElem,false,ANI_DURATION_HIDE_STATE);
},100,ANIMATION_PRIORITY);
};
var myProgressFunction = function(message, sender, state, url, params) {
if (state == "Done" || state == "Failed") {
hideState();
return;
}
if (sender == "stp_loadTiddlyWikiStore") {
idleCount = 0;
if (state == "Processing")
showState("Including...");
} else {
showState(message);
}
};
return myProgressFunction;
};
twWeb.setProgressFunction(config.extensions.SharedTiddlersPlugin.getDefaultProgressFunction());
//================================================================================
// The "describeNode" macro
//
// Syntax: <<describeNode nodeName {nodeUrl|self}>>
//
config.macros.describeNode = {};
config.macros.describeNode.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
var macroTWcode = wikifier.source.substring(wikifier.matchStart, wikifier.nextMatch);
createTiddlyText(createTiddlyElement(place,"code"),macroTWcode);
// node description duplicates are handled when adding, so no "handle only once" here
var includeURL = tiddler.getIncludeURL(),
nodeName = params[0],
urlParam = params[1],
self = (urlParam == "self");
if(self) {
var oldSelf = twWeb.getSelfNodeName();
if(oldSelf && (oldSelf != nodeName))
return alert("The \'"+oldSelf+"\' alias is already assigned as the name of the current "+
"TiddlyWiki; the new attempt to assign \'"+nodeName+"\' is ignored.");
twWeb.setSelfNodeName(nodeName);
return;
}
var url = resolveUrlFrom(urlParam, includeURL);
url = stp_resolveURL(url); // if no includeURL
twWeb.addNodeDesc(nodeName,url);
};
//================================================================================
// The "include" macro
//
// Syntax: <<include [url:]url [filters:filterLine] [substitute]
// [substituteShadows] [hide:hideFlag] [delay:delayDuration]>>
//
config.macros.include = {};
config.macros.include.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
var macroTWcode = wikifier.source.substring(wikifier.matchStart, wikifier.nextMatch),
pParams = paramString.parseParams("url",null,true,false,true); // allowEval, cascadeDefaults, names allowed
var hide = getFlag(pParams, "hide", false) || params.contains('hide');
if (!hide)
createTiddlyText(createTiddlyElement(place,"code"),macroTWcode);
if (twWeb.includeUsages.isUsed(macroTWcode))
return;
twWeb.includeUsages.setUsed(macroTWcode);
var urls = pParams[0]["url"],
includeParams = {
delayMilliSeconds: parseInt(getParam(pParams,"delay","0")),
filterLine: getParam(pParams,"filters","[all[-]]"),
substitute: params.contains('substitute'),
substituteShadows: params.contains('substituteShadows'),
noRefresh: params.contains('noRefresh'),
importMode: getParam(pParams,"import",undefined),
evalTiddlers: params.contains('eval'),
wikifyTiddlers: params.contains('wikify')
};
var checkUrlAndInclude = function(url) {
if(url == 'hide' || url == 'substituteShadows' || url == 'substitute' || url == 'eval' ||
url == 'wikify' || url == 'noRefresh')
return;
twWeb.include(url,includeParams);
};
for (var i = 0; urls && i < urls.length; i++)
checkUrlAndInclude(urls[i]);
};
//================================================================================
// The "reloadIncluded" macro
//
// Syntax: <<reloadIncluded [urls:urlsJSON] [reloadParams:otherReloadParamsJSON]
// [label:labelText] [tooltip:tooltipText] [class:className]>>
// (for reloadParams, see twWeb.reload)
//
config.macros.reloadIncluded = {};
config.macros.reloadIncluded.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
// parse params
var pParams = paramString.parseParams("url",null,true,false,true),
label = getParam(pParams,"label","refresh"),
tooltip = getParam(pParams,"tooltip",undefined),
elClass = getParam(pParams,"class"," "), // " " overwrites the default "button" class
urlsText = getParam(pParams,"urls",undefined),
otherReloadParamsText = getParam(pParams,"reloadParams","{}"),
reloadParams = JSON && JSON.parse(otherReloadParamsText) || jQuery.parseJSON(otherReloadParamsText);
reloadParams.urlOrNodeList = !urlsText ? undefined :
(JSON && JSON.parse(urlsText) || jQuery.parseJSON(urlsText));
if(!tooltip) {
if(reloadParams.urlOrNodeList) {
tooltip = "refresh '"+reloadParams.urlOrNodeList[0]+"'";
for(var i = 1; i < reloadParams.urlOrNodeList.length; i++)
tooltip += ", '"+reloadParams.urlOrNodeList[i]+"'";
tooltip += (i > 0) ? " nodes" : " node";
} else
tooltip = "refresh all included nodes";
}
// create button, add handler
createTiddlyButton(place,label,tooltip,function(){
var returnHere = function() {
if(twWeb.hasPendingIncludes()) {
invokeLater(returnHere,100);
return;
} // wait until all the stores are loaded and the page is refreshed
var t = tiddler.title, te = DEFAULT_VIEW_TEMPLATE;
story.displayTiddler(this,t,story.chooseTemplateForTiddler(t,te));
}
twWeb.reload(reloadParams);
invokeLater(returnHere,100); // wait a bit for the nodes to unload
},elClass);
};
//================================================================================
// The "includeState" macro
//
// Syntax: <<includeState>>
//
config.macros.includeState = {};
config.macros.includeState.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
var getFullState = function () {
var s = "";
var includes = twWeb.getIncludedStoresUrls();
if (!includes.length)
return "{{noIncludes{\nNo TiddlyWiki is included or including is disabled (see AdvancedOptions)\n}}}\n";
s += "|!Address|!State|\n";
for (var i = 0; i < includes.length; i++) {
var inc = includes[i];
s += "|{{{"+inc+"}}}|";
var t = twWeb.getState(inc);
s += t ? "{{{"+t+"}}}" : "included";
s += "|\n"
}
s += "|includeState|k\n";
return s;
};
var updateState = function(){
removeChildren(div);
wikify(getFullState(),div);
if (twWeb.hasPendingIncludes())
invokeLater(updateState,500,UPDATE_STATE_PRIORITY);
};
var div = createTiddlyElement(place,"div");
invokeLater(updateState,0,UPDATE_STATE_PRIORITY);
};
//================================================================================
// Change standart formatters
var getFormatterIndex = function(formatterName) {
for(var i = 0; i < config.formatters.length; i++)
if(config.formatters[i].name == formatterName)
return i;
return null;
}
//--------------------------------------------------------------------------------
// Change the prettyLink formatter so that it
// * recognizes [[text|target]]@nodeName and [[target]]@nodeName syntax
// * takes into account includeURL of the tiddler and propagates inclusion
if(config.options.STP_hijackPrettyLink) {
var prettyLinkFormatterIndex = getFormatterIndex("prettyLink");
config.extensions.SharedTiddlersPlugin.orig_prettyLinkFormatter = config.formatters[prettyLinkFormatterIndex];
config.formatters[prettyLinkFormatterIndex] = {
name: "prettyLink",
match: "\\[\\[",
lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\](?:(?:@(\w+))(?:@([\w\s\:]+)@)?)?/mg,
handler: function(w) {
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
// call the formatter
config.extensions.SharedTiddlersPlugin.orig_prettyLinkFormatter.handler(w);
// call the include.handler, if necessary
var node = lookaheadMatch[4]? lookaheadMatch[4] : undefined,
includeURL = w.tiddler? w.tiddler.getIncludeURL() : null,
urlParam = node? twWeb.setNodeNotation(node) : (includeURL? includeURL : undefined),
target = lookaheadMatch[3]? lookaheadMatch[3] : lookaheadMatch[1],
paramString = '"'+urlParam+'" filters:"[['+target+']]" hide noRefresh ';
if(lookaheadMatch[5])
paramString += lookaheadMatch[5];
if(urlParam)
config.macros.include.handler(w.output,"include",
paramString.readMacroParams(true),w,paramString,w.tiddler);
// move nextMatch according to this.lookaheadRegExp, not original prettyLink
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
};
}
//--------------------------------------------------------------------------------
// Change the image formatter so that it
// * takes into account includeURL of the tiddler and recalcs relative urls
if(config.options.STP_hijackImageFormatter) {
var imageFormatterIndex = getFormatterIndex("image");
config.extensions.SharedTiddlersPlugin.orig_imageFormatterHandler = config.formatters[imageFormatterIndex].handler;
config.formatters[imageFormatterIndex].handler = function(w) {
var lastChildBeforeHandling = w.output.lastChild;
config.extensions.SharedTiddlersPlugin.orig_imageFormatterHandler.apply(this,arguments);
if(w.output.lastChild != lastChildBeforeHandling) {
var img = w.output.lastChild,
includeURL = w.tiddler ? w.tiddler.getIncludeURL() : "",
imgSrc = jQuery(img).attr("src");
// take includeURL into account:
img.src = resolveUrlFrom(imgSrc, includeURL);
}
}
}
//================================================================================
// Add inline-management tools by hijacking .edit.handler
config.extensions.SharedTiddlersPlugin.orig_editHandler = config.macros.edit.handler;
config.macros.edit.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
var includeUrl = tiddler.getIncludeURL();
if(params[0] == "text" && includeUrl){ // only for "text", not other fields
var e = createTiddlyElement(null,"div");
e.className = "manageIncludedPanel";
createTiddlyText(e,"manage the included tiddler: ");
// go to the source
var sourceUrl = includeUrl + "#[["+tiddler.title+"]]";
createExternalLink(e, sourceUrl, "open in the source TiddlyWiki");
// view the link to the source
createTiddlyText(e," (");
createTiddlyButton(e,"view link","view the link to the source",function(e){
var popup = Popup.create(this);
createTiddlyText(popup,sourceUrl);
Popup.show();
var ev = e || window.event;
ev.cancelBubble = true;
if(ev.stopPropagation)
ev.stopPropagation();
return false;
}," ");
createTiddlyText(e,")");
// import
if(!readOnly) {
createTiddlyText(e," | ");
createTiddlyButton(e,"import","import this tiddler",function(){
twWeb.importAndLog(tiddler,1);
}," ");
}
// reload
createTiddlyText(e," | ");
config.macros.reloadIncluded.handler(e,"",null,null,'urls:\'["'+includeUrl+'"]\'',tiddler);
// other actions
// if the read only mode is not set, display all tools otherwise ...
place.appendChild(e);
}
return config.extensions.SharedTiddlersPlugin.orig_editHandler(place,macroName,params,wikifier,paramString,tiddler);
};
//================================================================================
// Perform plugin startup tasks
// add this for the "install only once" check (which also prevents conflicts with abego.IncludePlugin)
// (this is also deprecated API for backward compability)
abego.TiddlyWikiIncluder = {
getIncludes: twWeb.getIncludedStoresUrls,
getState: twWeb.getState,
getStore: twWeb.getStore
};
attachToStore();
invokeLater(includeFromIncludeList,100);
// add several more methods to the "API namespace"
window.sharedTiddlersAPI.getIncludes = twWeb.getIncludedStoresUrls;
window.sharedTiddlersAPI.getState = twWeb.getState;
window.sharedTiddlersAPI.getStore = twWeb.getStore;
window.sharedTiddlersAPI.importAndLog = twWeb.importAndLog;
// iterates over all tiddlers of "the store" and all tiddlers of included (and loaded) stores
//
window.sharedTiddlersAPI.forReallyEachTiddler = function(callback) {
var caller = function() {
store.forEachTiddler(callback);
};
getFunctionUsingForReallyEachTiddler(caller).call(store);
};
})();
//%/ //
/***
|Description|highlights saving button (bold red) when there's unsaved changes|
|Version|1.0|
|Short status|<<insertEditable container:"@stateComment" size:max cell>>|
|Author|Yakov Litvin|
***/
//{{{
// add the "saveChangesButton" class to the save changes button
config.macros.saveChanges.SCM_orig_handler = config.macros.saveChanges.handler;
config.macros.saveChanges.handler = function(place,macroName,params)
{
this.SCM_orig_handler.apply(this,arguments);
place.lastChild.classList.add("saveChangesButton");
};
// styles that highlight save button when there's something to save
var css = ".saveChangesButton { font-weight: bold; color: red !important; }";
TiddlyWiki.prototype.SCM_orig_setDirty = TiddlyWiki.prototype.setDirty;
TiddlyWiki.prototype.setDirty = function(dirty)
{
if(dirty)
setStylesheet(css,"highlightSaving");
else
removeStyleSheet("highlightSaving");
return this.SCM_orig_setDirty.apply(this,arguments);
};
//}}}
/***
|Name|TaggedTemplateTweak|
|Source|http://www.TiddlyTools.com/#TaggedTemplateTweak|
|Documentation|http://www.TiddlyTools.com/#TaggedTemplateTweakInfo|
|Version|1.6.1|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|use alternative ViewTemplate/EditTemplate for specific tiddlers|
This plugin extends the core function, {{{story.chooseTemplateForTiddler()}}}, so that any given tiddler can be viewed and/or edited using alternatives to the standard tiddler templates.
!!!!!Documentation
>see [[TaggedTemplateTweakInfo]]
!!!!!Code
***/
//{{{
version.extensions.TaggedTemplateTweak = {major: 1, minor: 6, revision: 1, date: new Date(2009,9,2)};
if (!config.options.txtTemplateTweakFieldname)
config.options.txtTemplateTweakFieldname = 'template';
Story.prototype.taggedTemplate_chooseTemplateForTiddler = Story.prototype.chooseTemplateForTiddler;
Story.prototype.chooseTemplateForTiddler = function(title,template)
{
// get core template and split into theme and template name
var coreTemplate = this.taggedTemplate_chooseTemplateForTiddler.apply(this,arguments);
var theme = "", template = coreTemplate;
var parts = template.split(config.textPrimitives.sectionSeparator);
if (parts[1]) { theme = parts[0]; template = parts[1]; }
else theme = config.options.txtTheme || ""; // if theme is not specified
theme += config.textPrimitives.sectionSeparator;
// look for template using title as prefix
if (!store.getTaggedTiddlers(title).length) { // if tiddler is not a tag
if (store.getTiddlerText(theme+title+template))
return theme+title+template; // theme##TitleTemplate
if (store.getTiddlerText(title+template))
return title+template; // TitleTemplate
}
// look for templates using custom field value as prefix
var v = store.getValue(title,config.options.txtTemplateTweakFieldname);
if (store.getTiddlerText(theme+v+template))
return theme+v+template; // theme##valueTemplate
if (store.getTiddlerText(v+template))
return v+template; // valueTemplate
// look for template using tags as prefix
var tiddler = store.getTiddler(title);
if (!tiddler) return coreTemplate; // tiddler doesn't exist... use core result
for (i = 0; i < tiddler.tags.length; i++) {
var t = tiddler.tags[i]+template; // add tag prefix to template
var c = t.substr(0,1).toUpperCase()+t.substr(1); // capitalized for WikiWord title
if (store.getTiddlerText(theme+t)) { return theme+t; } // theme##tagTemplate
if (store.getTiddlerText(theme+c)) { return theme+c; } // theme##TagTemplate
if (store.getTiddlerText(t)) { return t; } // tagTemplate
if (store.getTiddlerText(c)) { return c; } // TagTemplate
}
// no match... use core result
return coreTemplate;
}
//}}}
/***
Пример кода:
{{{
config.commands.closeTiddler.text = "close";
config.commands.closeTiddler.tooltip = "Close this tiddler";
config.commands.closeOthers.text = "close others";
config.commands.closeOthers.tooltip = "Close all other tiddlers";
}}}
(возможно, стоит заменить текст на картинки -- для mobile)
***/
//{{{
//}}}
/***
|Description|Replaces toolbar buttons with icons|
|Source|Lewcid TW {{DDnc{add the link}}}|
|Tweaked by|Yakov Litvin|
|Version|2.0|
!!!Usage
For each command that you want to set an icon for, just add a line like the following into a tiddler tagged {{{systemConfig}}}, specifying the icon image location:
{{{
config.commands.editTiddler.imgLoc = "jump.bmp";
}}}
No need to edit ViewTemplate!
!!!To do:
* fix "close" SVG: [[it creates bad "padding"|"close" SVG wrong "padding"]]
** then fix padding for images (horizontal = vertical)
* extend so that ~SVGs can be used as icons as well (directly, not using base64); or find out how to convert [[an SVG|closeOthers SVG]] to base64
***/
//{{{
// based on TW 2.7.1 core code
config.macros.toolbar.createCommand = function(place,commandName,tiddler,className)
{
if(typeof commandName != "string")
for(var t in config.commands)
if(config.commands[t] == commandName)
commandName = t;
if(!(tiddler instanceof Tiddler) || (typeof commandName != "string"))
return
var command = config.commands[commandName];
if(command.isEnabled ? !command.isEnabled(tiddler) :
!this.isCommandEnabled(command,tiddler))
return;
var text = command.getText ? command.getText(tiddler) : this.getCommandText(command,tiddler);
var tooltip = command.getTooltip ? command.getTooltip(tiddler) : this.getCommandTooltip(command,tiddler);
var cmd = command.type == "popup" ? this.onClickPopup : this.onClickCommand;
var btn = createTiddlyButton(null,text,tooltip,cmd);
btn.setAttribute("commandName",commandName);
btn.setAttribute("tiddler",tiddler.title);
jQuery(btn).addClass("command_" + commandName);
if(className)
jQuery(btn).addClass(className);
// main change:
if(command.imgLoc) {
btn.innerHTML = "<img src='"+command.imgLoc+"'>";
jQuery(btn).addClass("imageWrappingLink");
}
place.appendChild(btn);
}
setStylesheet(".toolbarImg {vertical-align: middle; cursor:pointer;}\n"+
".imageWrappingLink {display: inline-block;}\n", "commandIconStyles");
// base64 png work:
//config.commands.jump.imgLoc= "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAuCAIAAADcLmxdAAAAtUlEQVRYhe3QSw7EIAwD0Nz/0nQ1/cxQYjuRppWcLdh5EOMBE/8GjGHEMe9BRGF6EBUB6DDi3Yj0vhAxwggjjDCCRwQ2AiKtCirWi9jbjPhCgElEILRFmsQXU6DLBfAFjet/2ybtXQ68Z15dd1ANt70VB5tNSgWHElkfs6Xa5+nf2yVAEcgCWUAg1msqAg5xt6wooBGgg+5kA6lDKRQyC4fYpsWmDr1KTp4p1ZJivmWM+MwjEBvNTi/A51SXaAAAAABJRU5ErkJggg==";
// base64 SVG work as well:
//config.commands.closeTiddler.imgLoc = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjI0cHgiIGlkPSJMYXllcl8xIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0cHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxwYXRoIGQ9Ik0yMi4yNDUsNC4wMTVjMC4zMTMsMC4zMTMsMC4zMTMsMC44MjYsMCwxLjEzOWwtNi4yNzYsNi4yN2MtMC4zMTMsMC4zMTItMC4zMTMsMC44MjYsMCwxLjE0bDYuMjczLDYuMjcyICBjMC4zMTMsMC4zMTMsMC4zMTMsMC44MjYsMCwxLjE0bC0yLjI4NSwyLjI3N2MtMC4zMTQsMC4zMTItMC44MjgsMC4zMTItMS4xNDIsMGwtNi4yNzEtNi4yNzFjLTAuMzEzLTAuMzEzLTAuODI4LTAuMzEzLTEuMTQxLDAgIGwtNi4yNzYsNi4yNjdjLTAuMzEzLDAuMzEzLTAuODI4LDAuMzEzLTEuMTQxLDBsLTIuMjgyLTIuMjhjLTAuMzEzLTAuMzEzLTAuMzEzLTAuODI2LDAtMS4xNGw2LjI3OC02LjI2OSAgYzAuMzEzLTAuMzEyLDAuMzEzLTAuODI2LDAtMS4xNEwxLjcwOSw1LjE0N2MtMC4zMTQtMC4zMTMtMC4zMTQtMC44MjcsMC0xLjE0bDIuMjg0LTIuMjc4QzQuMzA4LDEuNDE3LDQuODIxLDEuNDE3LDUuMTM1LDEuNzMgIEwxMS40MDUsOGMwLjMxNCwwLjMxNCwwLjgyOCwwLjMxNCwxLjE0MSwwLjAwMWw2LjI3Ni02LjI2N2MwLjMxMi0wLjMxMiwwLjgyNi0wLjMxMiwxLjE0MSwwTDIyLjI0NSw0LjAxNXoiLz48L3N2Zz4=";
//}}}
/***
|Version|0.9.5|
|state|ready; stickToTheTop option needs more refinement; meta to be written|
!~ToDo
* add style sheet for better positioning of items inside menu (~left/right/center classes)
* refine sticky menu behaviour (see the code, especially "when a tiddler is opened on top" issue)
* check how this interacts with TiddlersBarPlugin
To edit: [[TopLineMenu]]
!Code
***/
//{{{
// menu generating handler; create a macro instead?
config.macros.topLineMenu = {
menuId: "topLineMenu",
tiddlerName: "TopLineMenu",
menuPlaceId: "displayArea",
beforeSelector: "#tiddlerDisplay",
stickToTheTop: true,
handler: function() {
if (document.getElementById(this.menuId))
return;
var place = document.getElementById(this.menuPlaceId);
var theMenu = document.createElement("div");
theMenu.setAttribute("id", this.menuId);
place.insertBefore(theMenu, place.firstChild);
wikify("<<tiddler [["+this.tiddlerName+"]]>>",theMenu);
if(this.stickToTheTop) {
theMenu.style.maxWidth = place.offsetWidth+"px";
//# make this adapt when a sidebar is hidden
var height = theMenu.offsetHeight;
//# update on this.tiddlerName content edit
jQuery(place).find(this.beforeSelector).css("margin-top",height+"px");
//# fix: when a tiddler is opened on top, this doesn't ensures tiddler's
// title is visible
// fix: with margins on startup theMenu was positioned
// to the top of tiddlerDisplay, not to the top of displayArea
jQuery(window).scroll();
//# set z-index to hover above CodeMirror editor
// may be of interest:
// - http://stackoverflow.com/questions/37849710/making-one-navbar-sticky-when-user-scrolls
}
},
init: function() {
if(!this.stickToTheTop) return;
jQuery(window).on("scroll",function(){
var menuElement = document.getElementById(config.macros.topLineMenu.menuId),
header = document.getElementsByClassName("header")[0],
headerBottom = header.offsetTop + header.offsetHeight - findScrollY(),
position = findScrollY() + Math.max(headerBottom,0);
menuElement.style.top = position+"px";
});
setStylesheet("#"+config.macros.topLineMenu.menuId+" {"+
"position:absolute;"+
"}","topLinePositionCSS")
}
};
// old-fashioned way to launch handler, copied from http://tiddlywiki.squize.org/#HoverMenuPlugin
window.old_restart_before_topLineMenu = restart;
restart = function() {
window.old_restart_before_topLineMenu();
config.macros.topLineMenu.handler();
};
// make TopLineMenu resistant to refreshing (refreshPageTemplate)
window.old_refreshPageTemplate_before_topLineMenu = refreshPageTemplate;
refreshPageTemplate = function(title) {
window.old_refreshPageTemplate_before_topLineMenu(title);
config.macros.topLineMenu.handler();
};
//}}}
/***
|''Name:''|YourSearchPlugin|
|''Version:''|2.1.6 (2012-04-19)|
|''Summary:''|Search your TiddlyWiki with advanced search features such as result lists, tiddler preview, result ranking, search filters, combined searches and many more.|
|''Source:''|http://tiddlywiki.abego-software.de/#YourSearchPlugin|
|''~CoreVersion:''|2.1|
|''Requires:''|~SavingWithRenamingFix|
|~|~SavingWithRenamingFix is not required, but if is used, should be launched first|
|''Twitter:''|[[@abego|https://twitter.com/#!/abego]]|
|''GitHub:''|https://github.com/abego/YourSearchPlugin|
|''Author:''|UdoBorkowski (ub [at] abego-software [dot] de)|
|''License:''|[[BSD open source license|http://www.abego-software.de/legal/apl-v10.html]]|
!About ~YourSearch
~YourSearch gives you a bunch of new features to simplify and speed up your daily searches in ~TiddlyWiki. It seamlessly integrates into the standard ~TiddlyWiki search: just start typing into the 'search' field and explore!
For more information see [[Help|YourSearch Help]].
!Source Code
***/
/***
This plugin's source code is compressed (and hidden).
Use this [[link|https://github.com/abego/YourSearchPlugin/blob/master/src/main/js/YourSearchPlugin-src.js]] to get the readable source code.
***/
///%
if(!version.extensions.YourSearchPlugin){version.extensions.YourSearchPlugin={major:2,minor:1,revision:6,source:"http://tiddlywiki.abego-software.de/#YourSearchPlugin",licence:"[[BSD open source license (abego Software)|http://www.abego-software.de/legal/apl-v10.html]]",copyright:"Copyright (c) abego Software GmbH, 2005-2012 (www.abego-software.de)"};if(!window.abego){window.abego={}}if(!Array.forEach){Array.forEach=function(c,e,d){for(var b=0,a=c.length;b<a;b++){e.call(d,c[b],b,c)}};Array.prototype.forEach=function(d,c){for(var b=0,a=this.length;b<a;b++){d.call(c,this[b],b,this)}}}abego.toInt=function(b,a){if(!b){return a}var c=parseInt(b);return(c==NaN)?a:c};abego.createEllipsis=function(a){var b=createTiddlyElement(a,"span");b.innerHTML="…"};abego.shallowCopy=function(b){if(!b){return b}var a={};for(var c in b){a[c]=b[c]}return a};abego.copyOptions=function(a){return !a?{}:abego.shallowCopy(a)};abego.countStrings=function(d,c){if(!c){return 0}var a=c.length;var f=0;var e=0;while(true){var b=d.indexOf(c,e);if(b<0){return f}f++;e=b+a}return f};abego.getBracedText=function(j,e,a){if(!e){e=0}var k=/\{([^\}]*)\}/gm;k.lastIndex=e;var d=k.exec(j);if(d){var l=d[1];var b=abego.countStrings(l,"{");if(!b){if(a){a.lastIndex=k.lastIndex}return l}var g=j.length;for(var f=k.lastIndex;f<g&&b;f++){var h=j.charAt(f);if(h=="{"){b++}else{if(h=="}"){b--}}}if(!b){if(a){a.lastIndex=f-1}return j.substring(d.index+1,f-1)}}};abego.select=function(d,c,b,a){if(!a){a=[]}d.forEach(function(e){if(c.call(b,e)){a.push(e)}});return a};abego.consumeEvent=function(a){if(a.stopPropagation){a.stopPropagation()}if(a.preventDefault){a.preventDefault()}a.cancelBubble=true;a.returnValue=true};abego.TiddlerFilterTerm=function(d,b){if(!b){b={}}var c=d;if(!b.textIsRegExp){c=d.escapeRegExp();if(b.fullWordMatch){c="\\b"+c+"\\b"}}var a=new RegExp(c,"m"+(b.caseSensitive?"":"i"));this.tester=new abego.MultiFieldRegExpTester(a,b.fields,b.withExtendedFields)};abego.TiddlerFilterTerm.prototype.test=function(a){return this.tester.test(a)};abego.parseNewTiddlerCommandLine=function(c){var a=/(.*?)\.(?:\s+|$)([^#]*)(#.*)?/.exec(c);if(!a){a=/([^#]*)()(#.*)?/.exec(c)}if(a){var d;if(a[3]){var b=a[3].replace(/#/g,"");d=b.parseParams("tag")}else{d=[[]]}var e=a[2]?a[2].trim():"";d.push({name:"text",value:e});d[0].text=[e];return{title:a[1].trim(),params:d}}else{return{title:c.trim(),params:[[]]}}};abego.parseTiddlerFilterTerm=function(queryText,offset,options){var re=/\s*(?:(?:\{([^\}]*)\})|(?:(=)|([#%!])|(?:(\w+)\s*\:(?!\/\/))|(?:(?:("(?:(?:\\")|[^"])+")|(?:\/((?:(?:\\\/)|[^\/])+)\/)|(\w+\:\/\/[^\s]+)|([^\s\)\-\"]+)))))/mg;var shortCuts={"!":"title","%":"text","#":"tags"};var fieldNames={};var fullWordMatch=false;re.lastIndex=offset;while(true){var i=re.lastIndex;var m=re.exec(queryText);if(!m||m.index!=i){throw"Word or String literal expected"}if(m[1]){var lastIndexRef={};var code=abego.getBracedText(queryText,0,lastIndexRef);if(!code){throw"Invalid {...} syntax"}var f=Function("tiddler","return ("+code+");");return{func:f,lastIndex:lastIndexRef.lastIndex,markRE:null}}if(m[2]){fullWordMatch=true}else{if(m[3]){fieldNames[shortCuts[m[3]]]=1}else{if(m[4]){fieldNames[m[4]]=1}else{var textIsRegExp=m[6];var text=m[5]?window.eval(m[5]):m[6]?m[6]:m[7]?m[7]:m[8];options=abego.copyOptions(options);options.fullWordMatch=fullWordMatch;options.textIsRegExp=textIsRegExp;var fields=[];for(var n in fieldNames){fields.push(n)}if(fields.length==0){options.fields=options.defaultFields}else{options.fields=fields;options.withExtendedFields=false}var term=new abego.TiddlerFilterTerm(text,options);var markREText=textIsRegExp?text:text.escapeRegExp();if(markREText&&fullWordMatch){markREText="\\b"+markREText+"\\b"}return{func:function(tiddler){return term.test(tiddler)},lastIndex:re.lastIndex,markRE:markREText?"(?:"+markREText+")":null}}}}}};abego.BoolExp=function(i,c,j){this.s=i;var h=j&&j.defaultOperationIs_OR;var e=/\s*\)/g;var f=/\s*(?:(and|\&\&)|(or|\|\|))/gi;var b=/\s*(\-|not)?(\s*\()?/gi;var a;var d=function(p){b.lastIndex=p;var l=b.exec(i);var o=false;var k=null;if(l&&l.index==p){p+=l[0].length;o=l[1];if(l[2]){var n=a(p);e.lastIndex=n.lastIndex;if(!e.exec(i)){throw"Missing ')'"}k={func:n.func,lastIndex:e.lastIndex,markRE:n.markRE}}}if(!k){k=c(i,p,j)}if(o){k.func=(function(m){return function(q){return !m(q)}})(k.func);k.markRE=null}return k};a=function(s){var n=d(s);while(true){var p=n.lastIndex;f.lastIndex=p;var k=f.exec(i);var o;var q;if(k&&k.index==p){o=!k[1];q=d(f.lastIndex)}else{try{q=d(p)}catch(r){return n}o=h}n.func=(function(t,m,l){return l?function(u){return t(u)||m(u)}:function(u){return t(u)&&m(u)}})(n.func,q.func,o);n.lastIndex=q.lastIndex;if(!n.markRE){n.markRE=q.markRE}else{if(q.markRE){n.markRE=n.markRE+"|"+q.markRE}}}};var g=a(0);this.evalFunc=g.func;if(g.markRE){this.markRegExp=new RegExp(g.markRE,j.caseSensitive?"mg":"img")}};abego.BoolExp.prototype.exec=function(){return this.evalFunc.apply(this,arguments)};abego.BoolExp.prototype.getMarkRegExp=function(){return this.markRegExp};abego.BoolExp.prototype.toString=function(){return this.s};abego.MultiFieldRegExpTester=function(b,a,c){this.re=b;this.fields=a?a:["title","text","tags"];this.withExtendedFields=c};abego.MultiFieldRegExpTester.prototype.test=function(b){var d=this.re;for(var a=0;a<this.fields.length;a++){var c=store.getValue(b,this.fields[a]);if(typeof c=="string"&&d.test(c)){return this.fields[a]}}if(this.withExtendedFields){return store.forEachField(b,function(e,g,f){return typeof f=="string"&&d.test(f)?g:null},true)}return null};abego.TiddlerQuery=function(b,a,d,c,e){if(d){this.regExp=new RegExp(b,a?"mg":"img");this.tester=new abego.MultiFieldRegExpTester(this.regExp,c,e)}else{this.expr=new abego.BoolExp(b,abego.parseTiddlerFilterTerm,{defaultFields:c,caseSensitive:a,withExtendedFields:e})}this.getQueryText=function(){return b};this.getUseRegExp=function(){return d};this.getCaseSensitive=function(){return a};this.getDefaultFields=function(){return c};this.getWithExtendedFields=function(){return e}};abego.TiddlerQuery.prototype.test=function(a){if(!a){return false}if(this.regExp){return this.tester.test(a)}return this.expr.exec(a)};abego.TiddlerQuery.prototype.filter=function(a){return abego.select(a,this.test,this)};abego.TiddlerQuery.prototype.getMarkRegExp=function(){if(this.regExp){return"".search(this.regExp)>=0?null:this.regExp}return this.expr.getMarkRegExp()};abego.TiddlerQuery.prototype.toString=function(){return(this.regExp?this.regExp:this.expr).toString()};abego.PageWiseRenderer=function(){this.firstIndexOnPage=0};merge(abego.PageWiseRenderer.prototype,{setItems:function(a){this.items=a;this.setFirstIndexOnPage(0)},getMaxPagesInNavigation:function(){return 10},getItemsCount:function(a){return this.items?this.items.length:0},getCurrentPageIndex:function(){return Math.floor(this.firstIndexOnPage/this.getItemsPerPage())},getLastPageIndex:function(){return Math.floor((this.getItemsCount()-1)/this.getItemsPerPage())},setFirstIndexOnPage:function(a){this.firstIndexOnPage=Math.min(Math.max(0,a),this.getItemsCount()-1)},getFirstIndexOnPage:function(){this.firstIndexOnPage=Math.floor(this.firstIndexOnPage/this.getItemsPerPage())*this.getItemsPerPage();return this.firstIndexOnPage},getLastIndexOnPage:function(){return Math.min(this.getFirstIndexOnPage()+this.getItemsPerPage()-1,this.getItemsCount()-1)},onPageChanged:function(a,b){},renderPage:function(a){if(a.beginRendering){a.beginRendering(this)}try{if(this.getItemsCount()){var d=this.getLastIndexOnPage();var c=-1;for(var b=this.getFirstIndexOnPage();b<=d;b++){c++;a.render(this,this.items[b],b,c)}}}finally{if(a.endRendering){a.endRendering(this)}}},addPageNavigation:function(c){if(!this.getItemsCount()){return}var k=this;var g=function(n){if(!n){n=window.event}abego.consumeEvent(n);var i=abego.toInt(this.getAttribute("page"),0);var m=k.getCurrentPageIndex();if(i==m){return}var l=i*k.getItemsPerPage();k.setFirstIndexOnPage(l);k.onPageChanged(i,m)};var e;var h=this.getCurrentPageIndex();var f=this.getLastPageIndex();if(h>0){e=createTiddlyButton(c,"Previous","Go to previous page (Shortcut: Alt-'<')",g,"prev");e.setAttribute("page",(h-1).toString());e.setAttribute("accessKey","<")}for(var d=-this.getMaxPagesInNavigation();d<this.getMaxPagesInNavigation();d++){var b=h+d;if(b<0){continue}if(b>f){break}var a=(d+h+1).toString();var j=b==h?"currentPage":"otherPage";e=createTiddlyButton(c,a,"Go to page %0".format([a]),g,j);e.setAttribute("page",(b).toString())}if(h<f){e=createTiddlyButton(c,"Next","Go to next page (Shortcut: Alt-'>')",g,"next");e.setAttribute("page",(h+1).toString());e.setAttribute("accessKey",">")}}});abego.LimitedTextRenderer=function(){var l=40;var c=4;var k=function(p,z,v){var q=p.length;if(q==0){p.push({start:z,end:v});return}var u=0;for(;u<q;u++){var w=p[u];if(w.start<=v&&z<=w.end){var o;var s=u+1;for(;s<q;s++){o=p[s];if(o.start>v||z>w.end){break}}var x=z;var y=v;for(var t=u;t<s;t++){o=p[t];x=Math.min(x,o.start);y=Math.max(y,o.end)}p.splice(u,s-u,{start:x,end:y});return}if(w.start>v){break}}p.splice(u,0,{start:z,end:v})};var d=function(n){var q=0;for(var p=0;p<n.length;p++){var o=n[p];q+=o.end-o.start}return q};var b=function(n){return(n>="a"&&n<="z")||(n>="A"&&n<="Z")||n=="_"};var f=function(p,r){if(!b(p[r])){return null}for(var o=r-1;o>=0&&b(p[o]);o--){}var q=o+1;var t=p.length;for(o=r+1;o<t&&b(p[o]);o++){}return{start:q,end:o}};var a=function(o,q,p){var n;if(p){n=f(o,q)}else{if(q<=0){return q}n=f(o,q-1)}if(!n){return q}if(p){if(n.start>=q-c){return n.start}if(n.end<=q+c){return n.end}}else{if(n.end<=q+c){return n.end}if(n.start>=q-c){return n.start}}return q};var j=function(r,q){var n=[];if(q){var u=0;do{q.lastIndex=u;var o=q.exec(r);if(o){if(u<o.index){var p=r.substring(u,o.index);n.push({text:p})}n.push({text:o[0],isMatch:true});u=o.index+o[0].length}else{n.push({text:r.substr(u)});break}}while(true)}else{n.push({text:r})}return n};var i=function(p){var n=0;for(var o=0;o<p.length;o++){if(p[o].isMatch){n++}}return n};var h=function(v,u,q,t,o){var w=Math.max(Math.floor(o/(t+1)),l);var n=Math.max(w-(q-u),0);var r=Math.min(Math.floor(q+n/3),v.length);var p=Math.max(r-w,0);p=a(v,p,true);r=a(v,r,false);return{start:p,end:r}};var m=function(r,y,o){var n=[];var v=i(r);var u=0;for(var p=0;p<r.length;p++){var x=r[p];var w=x.text;if(x.isMatch){var q=h(y,u,u+w.length,v,o);k(n,q.start,q.end)}u+=w.length}return n};var g=function(t,p,o){var n=o-d(p);while(n>0){if(p.length==0){k(p,0,a(t,o,false));return}else{var q=p[0];var v;var r;if(q.start==0){v=q.end;if(p.length>1){r=p[1].start}else{k(p,v,a(t,v+n,false));return}}else{v=0;r=q.start}var u=Math.min(r,v+n);k(p,v,u);n-=(u-v)}}};var e=function(p,x,w,n,o){if(n.length==0){return}var u=function(z,I,D,F,C){var H;var G;var E=0;var B=0;var A=0;for(;B<D.length;B++){H=D[B];G=H.text;if(F<E+G.length){A=F-E;break}E+=G.length}var y=C-F;for(;B<D.length&&y>0;B++){H=D[B];G=H.text.substr(A);A=0;if(G.length>y){G=G.substr(0,y)}if(H.isMatch){createTiddlyElement(z,"span",null,"marked",G)}else{createTiddlyText(z,G)}y-=G.length}if(C<I.length){abego.createEllipsis(z)}};if(n[0].start>0){abego.createEllipsis(p)}var q=o;for(var r=0;r<n.length&&q>0;r++){var t=n[r];var v=Math.min(t.end-t.start,q);u(p,x,w,t.start,t.start+v);q-=v}};this.render=function(p,q,o,t){if(q.length<o){o=q.length}var r=j(q,t);var n=m(r,q,o);g(q,n,o);e(p,q,r,n,o)}};(function(){function alertAndThrow(msg){alert(msg);throw msg}if(version.major<2||(version.major==2&&version.minor<1)){alertAndThrow("YourSearchPlugin requires TiddlyWiki 2.1 or newer.\n\nCheck the archive for YourSearch plugins\nsupporting older versions of TiddlyWiki.\n\nArchive: http://tiddlywiki.abego-software.de/archive")}abego.YourSearch={};var lastResults=undefined;var lastQuery=undefined;var setLastResults=function(array){lastResults=array};var getLastResults=function(){return lastResults?lastResults:[]};var getLastResultsCount=function(){return lastResults?lastResults.length:0};var matchInTitleWeight=4;var precisionInTitleWeight=10;var matchInTagsWeight=2;var getMatchCount=function(s,re){var m=s.match(re);return m?m.length:0};var standardRankFunction=function(tiddler,query){var markRE=query.getMarkRegExp();if(!markRE){return 1}var matchesInTitle=tiddler.title.match(markRE);var nMatchesInTitle=matchesInTitle?matchesInTitle.length:0;var nMatchesInTags=getMatchCount(tiddler.getTags(),markRE);var lengthOfMatchesInTitle=matchesInTitle?matchesInTitle.join("").length:0;var precisionInTitle=tiddler.title.length>0?lengthOfMatchesInTitle/tiddler.title.length:0;var rank=nMatchesInTitle*matchInTitleWeight+nMatchesInTags*matchInTagsWeight+precisionInTitle*precisionInTitleWeight+1;return rank};var findMatches=function(store,searchText,caseSensitive,useRegExp,sortField,excludeTag){lastQuery=null;var candidates=store.reverseLookup("tags",excludeTag,false);try{var defaultFields=[];if(config.options.chkSearchInTitle){defaultFields.push("title")}if(config.options.chkSearchInText){defaultFields.push("text")}if(config.options.chkSearchInTags){defaultFields.push("tags")}lastQuery=new abego.TiddlerQuery(searchText,caseSensitive,useRegExp,defaultFields,config.options.chkSearchExtendedFields)}catch(e){return[]}var results=lastQuery.filter(candidates);var rankFunction=abego.YourSearch.getRankFunction();for(var i=0;i<results.length;i++){var tiddler=results[i];var rank=rankFunction(tiddler,lastQuery);tiddler.searchRank=rank}if(!sortField){sortField="title"}var sortFunction=function(a,b){var searchRankDiff=a.searchRank-b.searchRank;if(searchRankDiff==0){if(a[sortField]==b[sortField]){return(0)}else{return(a[sortField]<b[sortField])?-1:+1}}else{return(searchRankDiff>0)?-1:+1}};results.sort(sortFunction);return results};var maxCharsInTitle=80;var maxCharsInTags=50;var maxCharsInText=250;var maxCharsInField=50;var itemsPerPageDefault=25;var itemsPerPageWithPreviewDefault=10;var yourSearchResultID="yourSearchResult";var yourSearchResultItemsID="yourSearchResultItems";var lastSearchText=null;var resultElement=null;var searchInputField=null;var searchButton=null;var lastNewTiddlerButton=null;var initStylesheet=function(){if(version.extensions.YourSearchPlugin.styleSheetInited){return}version.extensions.YourSearchPlugin.styleSheetInited=true;setStylesheet(store.getTiddlerText("YourSearchStyleSheet"),"yourSearch")};var isResultOpen=function(){return resultElement!=null&&resultElement.parentNode==document.body};var closeResult=function(){if(isResultOpen()){document.body.removeChild(resultElement)}};var closeResultAndDisplayTiddler=function(e){closeResult();var title=this.getAttribute("tiddlyLink");if(title){var withHilite=this.getAttribute("withHilite");var oldHighlightHack=highlightHack;if(withHilite&&withHilite=="true"&&lastQuery){highlightHack=lastQuery.getMarkRegExp()}story.displayTiddler(this,title);highlightHack=oldHighlightHack}return(false)};var adjustResultPositionAndSize=function(){if(!searchInputField){return}var root=searchInputField;var rootLeft=findPosX(root);var rootTop=findPosY(root);var rootHeight=root.offsetHeight;var popupLeft=rootLeft;var popupTop=rootTop+rootHeight;var winWidth=findWindowWidth();if(winWidth<resultElement.offsetWidth){resultElement.style.width=(winWidth-100)+"px";winWidth=findWindowWidth()}var popupWidth=resultElement.offsetWidth;if(popupLeft+popupWidth>winWidth){popupLeft=winWidth-popupWidth-30}if(popupLeft<0){popupLeft=0}resultElement.style.left=popupLeft+"px";resultElement.style.top=popupTop+"px";resultElement.style.display="block"};var scrollVisible=function(){if(resultElement){window.scrollTo(0,ensureVisible(resultElement))}if(searchInputField){window.scrollTo(0,ensureVisible(searchInputField))}};var ensureResultIsDisplayedNicely=function(){adjustResultPositionAndSize();scrollVisible()};var indexInPage=undefined;var currentTiddler=undefined;var pager=new abego.PageWiseRenderer();var MyItemRenderer=function(parent){this.itemHtml=store.getTiddlerText("YourSearchItemTemplate");if(!this.itemHtml){alertAndThrow("YourSearchItemTemplate not found")}this.place=document.getElementById(yourSearchResultItemsID);if(!this.place){this.place=createTiddlyElement(parent,"div",yourSearchResultItemsID)}};merge(MyItemRenderer.prototype,{render:function(pager,object,index,indexOnPage){indexInPage=indexOnPage;currentTiddler=object;var item=createTiddlyElement(this.place,"div",null,"yourSearchItem");item.innerHTML=this.itemHtml;applyHtmlMacros(item,null);refreshElements(item,null)},endRendering:function(pager){currentTiddler=null}});var refreshResult=function(){if(!resultElement||!searchInputField){return}var html=store.getTiddlerText("YourSearchResultTemplate");if(!html){html="<b>Tiddler YourSearchResultTemplate not found</b>"}resultElement.innerHTML=html;applyHtmlMacros(resultElement,null);refreshElements(resultElement,null);var itemRenderer=new MyItemRenderer(resultElement);pager.renderPage(itemRenderer);ensureResultIsDisplayedNicely()};pager.getItemsPerPage=function(){var n=(config.options.chkPreviewText)?abego.toInt(config.options.txtItemsPerPageWithPreview,itemsPerPageWithPreviewDefault):abego.toInt(config.options.txtItemsPerPage,itemsPerPageDefault);return(n>0)?n:1};pager.onPageChanged=function(){refreshResult()};var reopenResultIfApplicable=function(){if(searchInputField==null||!config.options.chkUseYourSearch){return}if((searchInputField.value==lastSearchText)&&lastSearchText&&!isResultOpen()){if(resultElement&&(resultElement.parentNode!=document.body)){document.body.appendChild(resultElement);ensureResultIsDisplayedNicely()}else{abego.YourSearch.onShowResult(true)}}};var invalidateResult=function(){closeResult();resultElement=null;lastSearchText=null};var isDescendantOrSelf=function(self,e){while(e!=null){if(self==e){return true}e=e.parentNode}return false};var onDocumentClick=function(e){if(e.target==searchInputField){return}if(e.target==searchButton){return}if(resultElement&&isDescendantOrSelf(resultElement,e.target)){return}closeResult()};var onDocumentKeyup=function(e){if(e.keyCode==27){closeResult()}};addEvent(document,"click",onDocumentClick);addEvent(document,"keyup",onDocumentKeyup);var myStorySearch=function(text,useCaseSensitive,useRegExp){lastSearchText=text;setLastResults(findMatches(store,text,useCaseSensitive,useRegExp,"title","excludeSearch"));abego.YourSearch.onShowResult()};var myMacroSearchHandler=function(place,macroName,params,wikifier,paramString,tiddler){initStylesheet();lastSearchText="";var searchTimeout=null;var doSearch=function(txt){if(config.options.chkUseYourSearch){myStorySearch(txt.value,config.options.chkCaseSensitiveSearch,config.options.chkRegExpSearch)}else{story.search(txt.value,config.options.chkCaseSensitiveSearch,config.options.chkRegExpSearch)}lastSearchText=txt.value};var clickHandler=function(e){doSearch(searchInputField);return false};var keyHandler=function(e){if(!e){e=window.event}searchInputField=this;switch(e.keyCode){case 13:if(e.ctrlKey&&lastNewTiddlerButton&&isResultOpen()){lastNewTiddlerButton.onclick.apply(lastNewTiddlerButton,[e])}else{doSearch(this)}break;case 27:if(isResultOpen()){closeResult()}else{this.value="";clearMessage()}break}if(String.fromCharCode(e.keyCode)==this.accessKey||e.altKey){reopenResultIfApplicable()}if(this.value.length<3&&searchTimeout){clearTimeout(searchTimeout)}if(this.value.length>2){if(this.value!=lastSearchText){if(!config.options.chkUseYourSearch||config.options.chkSearchAsYouType){if(searchTimeout){clearTimeout(searchTimeout)}var txt=this;searchTimeout=setTimeout(function(){doSearch(txt)},500)}}else{if(searchTimeout){clearTimeout(searchTimeout)}}}if(this.value.length==0){closeResult()}};var focusHandler=function(e){this.select();clearMessage();reopenResultIfApplicable()};var args=paramString.parseParams("list",null,true);var buttonAtRight=getFlag(args,"buttonAtRight");var sizeTextbox=getParam(args,"sizeTextbox",this.sizeTextbox);var txt=createTiddlyElement(null,"input",null,"txtOptionInput searchField",null);if(params[0]){txt.value=params[0]}txt.onkeyup=keyHandler;txt.onfocus=focusHandler;txt.setAttribute("size",sizeTextbox);txt.setAttribute("accessKey",this.accessKey);txt.setAttribute("autocomplete","off");if(config.browser.isSafari){txt.setAttribute("type","search");txt.setAttribute("results","5")}else{if(!config.browser.isIE){txt.setAttribute("type","text")}}var btn=createTiddlyButton(null,this.label,this.prompt,clickHandler);if(place){if(!buttonAtRight){place.appendChild(btn)}place.appendChild(txt);if(buttonAtRight){place.appendChild(btn)}}searchInputField=txt;searchButton=btn};var openAllFoundTiddlers=function(){closeResult();var results=getLastResults();var n=results.length;if(n){var titles=[];for(var i=0;i<n;i++){titles.push(results[i].title)}story.displayTiddlers(null,titles)}};var createOptionWithRefresh=function(place,optionParams,wikifier,tiddler){invokeMacro(place,"option",optionParams,wikifier,tiddler);var elem=place.lastChild;var oldOnClick=elem.onclick;elem.onclick=function(e){var result=oldOnClick.apply(this,arguments);refreshResult();return result};return elem};var removeTextDecoration=function(s){var removeThis=["''","{{{","}}}","//","<<<","/***","***/"];var reText="";for(var i=0;i<removeThis.length;i++){if(i!=0){reText+="|"}reText+="("+removeThis[i].escapeRegExp()+")"}return s.replace(new RegExp(reText,"mg"),"").trim()};var getShortCutNumber=function(){var i=indexInPage;return(i>=0&&i<=9)?(i<9?(i+1):0):-1};var limitedTextRenderer=new abego.LimitedTextRenderer();var renderLimitedText=function(place,s,maxLen){limitedTextRenderer.render(place,s,maxLen,lastQuery.getMarkRegExp())};var oldTiddlyWikiSaveTiddler=TiddlyWiki.prototype.saveTiddler;TiddlyWiki.prototype.saveTiddler=function(title,newTitle,newBody,modifier,modified,tags,fields){oldTiddlyWikiSaveTiddler.apply(this,arguments);invalidateResult()};var oldTiddlyWikiRemoveTiddler=TiddlyWiki.prototype.removeTiddler;TiddlyWiki.prototype.removeTiddler=function(title){oldTiddlyWikiRemoveTiddler.apply(this,arguments);invalidateResult()};config.macros.yourSearch={label:"yourSearch",prompt:"Gives access to the current/last YourSearch result",handler:function(place,macroName,params,wikifier,paramString,tiddler){if(params.length==0){return}var name=params[0];var func=config.macros.yourSearch.funcs[name];if(func){func(place,macroName,params,wikifier,paramString,tiddler)}},tests:{"true":function(){return true},"false":function(){return false},found:function(){return getLastResultsCount()>0},previewText:function(){return config.options.chkPreviewText}},funcs:{itemRange:function(place){if(getLastResultsCount()){var lastIndex=pager.getLastIndexOnPage();var s="%0 - %1".format([pager.getFirstIndexOnPage()+1,lastIndex+1]);createTiddlyText(place,s)}},count:function(place){createTiddlyText(place,getLastResultsCount().toString())},query:function(place){if(lastQuery){createTiddlyText(place,lastQuery.toString())}},version:function(place){var t="YourSearch %0.%1.%2".format([version.extensions.YourSearchPlugin.major,version.extensions.YourSearchPlugin.minor,version.extensions.YourSearchPlugin.revision]);var e=createTiddlyElement(place,"a");e.setAttribute("href","http://tiddlywiki.abego-software.de/#YourSearchPlugin");e.innerHTML='<font color="black" face="Arial, Helvetica, sans-serif">'+t+"<font>"},copyright:function(place){var e=createTiddlyElement(place,"a");e.setAttribute("href","http://www.abego-software.de");e.innerHTML='<font color="black" face="Arial, Helvetica, sans-serif">© 2005-2008 <b><font color="red">abego</font></b> Software<font>'},newTiddlerButton:function(place){if(lastQuery){var r=abego.parseNewTiddlerCommandLine(lastQuery.getQueryText());var btn=config.macros.newTiddler.createNewTiddlerButton(place,r.title,r.params,"new tiddler","Create a new tiddler based on search text. (Shortcut: Ctrl-Enter; Separators: '.', '#')",null,"text");var oldOnClick=btn.onclick;btn.onclick=function(){closeResult();oldOnClick.apply(this,arguments)};lastNewTiddlerButton=btn}},linkButton:function(place,macroName,params,wikifier,paramString,tiddler){if(params<2){return}var tiddlyLink=params[1];var text=params<3?tiddlyLink:params[2];var tooltip=params<4?text:params[3];var accessKey=params<5?null:params[4];var btn=createTiddlyButton(place,text,tooltip,closeResultAndDisplayTiddler,null,null,accessKey);btn.setAttribute("tiddlyLink",tiddlyLink)},closeButton:function(place,macroName,params,wikifier,paramString,tiddler){createTiddlyButton(place,"close","Close the Search Results (Shortcut: ESC)",closeResult)},openAllButton:function(place,macroName,params,wikifier,paramString,tiddler){var n=getLastResultsCount();if(n==0){return}var title=n==1?"open tiddler":"open all %0 tiddlers".format([n]);var button=createTiddlyButton(place,title,"Open all found tiddlers (Shortcut: Alt-O)",openAllFoundTiddlers);button.setAttribute("accessKey","O")},naviBar:function(place,macroName,params,wikifier,paramString,tiddler){pager.addPageNavigation(place)},"if":function(place,macroName,params,wikifier,paramString,tiddler){if(params.length<2){return}var testName=params[1];var negate=(testName=="not");if(negate){if(params.length<3){return}testName=params[2]}var test=config.macros.yourSearch.tests[testName];var showIt=false;try{if(test){showIt=test(place,macroName,params,wikifier,paramString,tiddler)!=negate}else{showIt=(!eval(testName))==negate}}catch(ex){}if(!showIt){place.style.display="none"}},chkPreviewText:function(place,macroName,params,wikifier,paramString,tiddler){var elem=createOptionWithRefresh(place,"chkPreviewText",wikifier,tiddler);elem.setAttribute("accessKey","P");elem.title="Show text preview of found tiddlers (Shortcut: Alt-P)";return elem}}};config.macros.foundTiddler={label:"foundTiddler",prompt:"Provides information on the tiddler currently processed on the YourSearch result page",handler:function(place,macroName,params,wikifier,paramString,tiddler){var name=params[0];var func=config.macros.foundTiddler.funcs[name];if(func){func(place,macroName,params,wikifier,paramString,tiddler)}},funcs:{title:function(place,macroName,params,wikifier,paramString,tiddler){if(!currentTiddler){return}var shortcutNumber=getShortCutNumber();var tooltip=shortcutNumber>=0?"Open tiddler (Shortcut: Alt-%0)".format([shortcutNumber.toString()]):"Open tiddler";var btn=createTiddlyButton(place,null,tooltip,closeResultAndDisplayTiddler,null);btn.setAttribute("tiddlyLink",currentTiddler.title);btn.setAttribute("withHilite","true");renderLimitedText(btn,currentTiddler.title,maxCharsInTitle);if(shortcutNumber>=0){btn.setAttribute("accessKey",shortcutNumber.toString())}},tags:function(place,macroName,params,wikifier,paramString,tiddler){if(!currentTiddler){return}renderLimitedText(place,currentTiddler.getTags(),maxCharsInTags)},text:function(place,macroName,params,wikifier,paramString,tiddler){if(!currentTiddler){return}renderLimitedText(place,removeTextDecoration(currentTiddler.text),maxCharsInText)},field:function(place,macroName,params,wikifier,paramString,tiddler){if(!currentTiddler){return}var name=params[1];var len=params.length>2?abego.toInt(params[2],maxCharsInField):maxCharsInField;var v=store.getValue(currentTiddler,name);if(v){renderLimitedText(place,removeTextDecoration(v),len)}},number:function(place,macroName,params,wikifier,paramString,tiddler){var numberToDisplay=getShortCutNumber();if(numberToDisplay>=0){var text="%0)".format([numberToDisplay.toString()]);createTiddlyElement(place,"span",null,"shortcutNumber",text)}}}};var opts={chkUseYourSearch:true,chkPreviewText:true,chkSearchAsYouType:true,chkSearchInTitle:true,chkSearchInText:true,chkSearchInTags:true,chkSearchExtendedFields:true,txtItemsPerPage:itemsPerPageDefault,txtItemsPerPageWithPreview:itemsPerPageWithPreviewDefault};for(var n in opts){if(config.options[n]==undefined){config.options[n]=opts[n]}}config.shadowTiddlers.AdvancedOptions+="\n<<option chkUseYourSearch>> Use 'Your Search' //([[more options|YourSearch Options]]) ([[help|YourSearch Help]])// ";config.shadowTiddlers["YourSearch Help"]="!Field Search\nWith the Field Search you can restrict your search to certain fields of a tiddler, e.g only search the tags or only the titles. The general form is //fieldname//'':''//textToSearch// (e.g. {{{title:intro}}}). In addition one-character shortcuts are also supported for the standard fields {{{title}}}, {{{text}}} and {{{tags}}}:\n|!What you want|!What you type|!Example|\n|Search ''titles only''|start word with ''!''|{{{!jonny}}} (shortcut for {{{title:jonny}}})|\n|Search ''contents/text only''|start word with ''%''|{{{%football}}} (shortcut for {{{text:football}}})|\n|Search ''tags only''|start word with ''#''|{{{#Plugin}}} (shortcut for {{{tags:Plugin}}})|\n\nUsing this feature you may also search the extended fields (\"Metadata\") introduced with TiddlyWiki 2.1, e.g. use {{{priority:1}}} to find all tiddlers with the priority field set to \"1\".\n\nYou may search a word in more than one field. E.g. {{{!#Plugin}}} (or {{{title:tags:Plugin}}} in the \"long form\") finds tiddlers containing \"Plugin\" either in the title or in the tags (but does not look for \"Plugin\" in the text). \n\n!Boolean Search\nThe Boolean Search is useful when searching for multiple words.\n|!What you want|!What you type|!Example|\n|''All words'' must exist|List of words|{{{jonny jeremy}}} (or {{{jonny and jeremy}}})|\n|''At least one word'' must exist|Separate words by ''or''|{{{jonny or jeremy}}}|\n|A word ''must not exist''|Start word with ''-''|{{{-jonny}}} (or {{{not jonny}}})|\n\n''Note:'' When you specify two words, separated with a space, YourSearch finds all tiddlers that contain both words, but not necessarily next to each other. If you want to find a sequence of word, e.g. '{{{John Brown}}}', you need to put the words into quotes. I.e. you type: {{{\"john brown\"}}}.\n\nUsing parenthesis you may change the default \"left to right\" evaluation of the boolean search. E.g. {{{not (jonny or jeremy)}}} finds all tiddlers that contain neither \"jonny\" nor \"jeremy. In contrast to this {{{not jonny or jeremy}}} (i.e. without parenthesis) finds all tiddlers that either don't contain \"jonny\" or that contain \"jeremy\".\n\n!'Exact Word' Search\nBy default a search result all matches that 'contain' the searched text. E.g. if you search for {{{Task}}} you will get all tiddlers containing 'Task', but also '~CompletedTask', '~TaskForce' etc.\n\nIf you only want to get the tiddlers that contain 'exactly the word' you need to prefix it with a '='. E.g. typing '=Task' will find the tiddlers that contain the word 'Task', ignoring words that just contain 'Task' as a substring.\n\n!~CaseSensitiveSearch and ~RegExpSearch\nThe standard search options ~CaseSensitiveSearch and ~RegExpSearch are fully supported by YourSearch. However when ''~RegExpSearch'' is on Filtered and Boolean Search are disabled.\n\nIn addition you may do a \"regular expression\" search even with the ''~RegExpSearch'' set to false by directly entering the regular expression into the search field, framed with {{{/.../}}}. \n\nExample: {{{/m[ae][iy]er/}}} will find all tiddlers that contain either \"maier\", \"mayer\", \"meier\" or \"meyer\".\n\n!~JavaScript Expression Filtering\nIf you are familiar with JavaScript programming and know some TiddlyWiki internals you may also use JavaScript expression for the search. Just enter a JavaScript boolean expression into the search field, framed with {{{ { ... } }}}. In the code refer to the variable tiddler and evaluate to {{{true}}} when the given tiddler should be included in the result. \n\nExample: {{{ { tiddler.modified > new Date(\"Jul 4, 2005\")} }}} returns all tiddler modified after July 4th, 2005.\n\n!Combined Search\nYou are free to combine the various search options. \n\n''Examples''\n|!What you type|!Result|\n|{{{!jonny !jeremy -%football}}}|all tiddlers with both {{{jonny}}} and {{{jeremy}}} in its titles, but no {{{football}}} in content.|\n|{{{#=Task}}}|All tiddlers tagged with 'Task' (the exact word). Tags named '~CompletedTask', '~TaskForce' etc. are not considered.|\n\n!Access Keys\nYou are encouraged to use the access keys (also called \"shortcut\" keys) for the most frequently used operations. For quick reference these shortcuts are also mentioned in the tooltip for the various buttons etc.\n\n|!Key|!Operation|\n|{{{Alt-F}}}|''The most important keystroke'': It moves the cursor to the search input field so you can directly start typing your query. Pressing {{{Alt-F}}} will also display the previous search result. This way you can quickly display multiple tiddlers using \"Press {{{Alt-F}}}. Select tiddler.\" sequences.|\n|{{{ESC}}}|Closes the [[YourSearch Result]]. When the [[YourSearch Result]] is already closed and the cursor is in the search input field the field's content is cleared so you start a new query.|\n|{{{Alt-1}}}, {{{Alt-2}}},... |Pressing these keys opens the first, second etc. tiddler from the result list.|\n|{{{Alt-O}}}|Opens all found tiddlers.|\n|{{{Alt-P}}}|Toggles the 'Preview Text' mode.|\n|{{{Alt-'<'}}}, {{{Alt-'>'}}}|Displays the previous or next page in the [[YourSearch Result]].|\n|{{{Return}}}|When you have turned off the 'as you type' search mode pressing the {{{Return}}} key actually starts the search (as does pressing the 'search' button).|\n\n//If some of these shortcuts don't work for you check your browser if you have other extensions installed that already \"use\" these shortcuts.//";config.shadowTiddlers["YourSearch Options"]="|>|!YourSearch Options|\n|>|<<option chkUseYourSearch>> Use 'Your Search'|\n|!|<<option chkPreviewText>> Show Text Preview|\n|!|<<option chkSearchAsYouType>> 'Search As You Type' Mode (No RETURN required to start search)|\n|!|Default Search Filter:<<option chkSearchInTitle>>Title ('!') <<option chkSearchInText>>Text ('%') <<option chkSearchInTags>>Tags ('#') <<option chkSearchExtendedFields>>Extended Fields<html><br><font size=\"-2\">The fields of a tiddlers that are searched when you don't explicitly specify a filter in the search text <br>(Explictly specify fields using one or more '!', '%', '#' or 'fieldname:' prefix before the word/text to find).</font></html>|\n|!|Number of items on search result page: <<option txtItemsPerPage>>|\n|!|Number of items on search result page with preview text: <<option txtItemsPerPageWithPreview>>|\n";config.shadowTiddlers.YourSearchStyleSheet="/***\n!~YourSearchResult Stylesheet\n***/\n/*{{{*/\n.yourSearchResult {\n\tposition: absolute;\n\twidth: 800px;\n\n\tpadding: 0.2em;\n\tlist-style: none;\n\tmargin: 0;\n\n\tbackground: #ffd;\n\tborder: 1px solid DarkGray;\n}\n\n/*}}}*/\n/***\n!!Summary Section\n***/\n/*{{{*/\n.yourSearchResult .summary {\n\tborder-bottom-width: thin;\n\tborder-bottom-style: solid;\n\tborder-bottom-color: #999999;\n\tpadding-bottom: 4px;\n}\n\n.yourSearchRange, .yourSearchCount, .yourSearchQuery {\n\tfont-weight: bold;\n}\n\n.yourSearchResult .summary .button {\n\tfont-size: 10px;\n\n\tpadding-left: 0.3em;\n\tpadding-right: 0.3em;\n}\n\n.yourSearchResult .summary .chkBoxLabel {\n\tfont-size: 10px;\n\n\tpadding-right: 0.3em;\n}\n\n/*}}}*/\n/***\n!!Items Area\n***/\n/*{{{*/\n.yourSearchResult .marked {\n\tbackground: none;\n\tfont-weight: bold;\n}\n\n.yourSearchItem {\n\tmargin-top: 2px;\n}\n\n.yourSearchNumber {\n\tcolor: #808080;\n}\n\n\n.yourSearchTags {\n\tcolor: #008000;\n}\n\n.yourSearchText {\n\tcolor: #808080;\n\tmargin-bottom: 6px;\n}\n\n/*}}}*/\n/***\n!!Footer\n***/\n/*{{{*/\n.yourSearchFooter {\n\tmargin-top: 8px;\n\tborder-top-width: thin;\n\tborder-top-style: solid;\n\tborder-top-color: #999999;\n}\n\n.yourSearchFooter a:hover{\n\tbackground: none;\n\tcolor: none;\n}\n/*}}}*/\n/***\n!!Navigation Bar\n***/\n/*{{{*/\n.yourSearchNaviBar a {\n\tfont-size: 16px;\n\tmargin-left: 4px;\n\tmargin-right: 4px;\n\tcolor: black;\n\ttext-decoration: underline;\n}\n\n.yourSearchNaviBar a:hover {\n\tbackground-color: none;\n}\n\n.yourSearchNaviBar .prev {\n\tfont-weight: bold;\n\tcolor: blue;\n}\n\n.yourSearchNaviBar .currentPage {\n\tcolor: #FF0000;\n\tfont-weight: bold;\n\ttext-decoration: none;\n}\n\n.yourSearchNaviBar .next {\n\tfont-weight: bold;\n\tcolor: blue;\n}\n/*}}}*/\n";config.shadowTiddlers.YourSearchResultTemplate='<!--\n{{{\n-->\n<span macro="yourSearch if found">\n<!-- The Summary Header ============================================ -->\n<table class="summary" border="0" width="100%" cellspacing="0" cellpadding="0"><tbody>\n <tr>\n\t<td align="left">\n\t\tYourSearch Result <span class="yourSearchRange" macro="yourSearch itemRange"></span>\n\t\t of <span class="yourSearchCount" macro="yourSearch count"></span>\n\t\tfor <span class="yourSearchQuery" macro="yourSearch query"></span>\n\t</td>\n\t<td class="yourSearchButtons" align="right">\n\t\t<span macro="yourSearch chkPreviewText"></span><span class="chkBoxLabel">preview text</span>\n\t\t<span macro="yourSearch newTiddlerButton"></span>\n\t\t<span macro="yourSearch openAllButton"></span>\n\t\t<span macro="yourSearch linkButton \'YourSearch Options\' options \'Configure YourSearch\'"></span>\n\t\t<span macro="yourSearch linkButton \'YourSearch Help\' help \'Get help how to use YourSearch\'"></span>\n\t\t<span macro="yourSearch closeButton"></span>\n\t</td>\n </tr>\n</tbody></table>\n\n<!-- The List of Found Tiddlers ============================================ -->\n<div id="yourSearchResultItems" itemsPerPage="25" itemsPerPageWithPreview="10"></div>\n\n<!-- The Footer (with the Navigation) ============================================ -->\n<table class="yourSearchFooter" border="0" width="100%" cellspacing="0" cellpadding="0"><tbody>\n <tr>\n\t<td align="left">\n\t\tResult page: <span class="yourSearchNaviBar" macro="yourSearch naviBar"></span>\n\t</td>\n\t<td align="right"><span macro="yourSearch version"></span>, <span macro="yourSearch copyright"></span>\n\t</td>\n </tr>\n</tbody></table>\n<!-- end of the \'tiddlers found\' case =========================================== -->\n</span>\n\n\n<!-- The "No tiddlers found" case =========================================== -->\n<span macro="yourSearch if not found">\n<table class="summary" border="0" width="100%" cellspacing="0" cellpadding="0"><tbody>\n <tr>\n\t<td align="left">\n\t\tYourSearch Result: No tiddlers found for <span class="yourSearchQuery" macro="yourSearch query"></span>.\n\t</td>\n\t<td class="yourSearchButtons" align="right">\n\t\t<span macro="yourSearch newTiddlerButton"></span>\n\t\t<span macro="yourSearch linkButton \'YourSearch Options\' options \'Configure YourSearch\'"></span>\n\t\t<span macro="yourSearch linkButton \'YourSearch Help\' help \'Get help how to use YourSearch\'"></span>\n\t\t<span macro="yourSearch closeButton"></span>\n\t</td>\n </tr>\n</tbody></table>\n</span>\n\n\n<!--\n}}}\n-->\n';config.shadowTiddlers.YourSearchItemTemplate="<!--\n{{{\n-->\n<span class='yourSearchNumber' macro='foundTiddler number'></span>\n<span class='yourSearchTitle' macro='foundTiddler title'/></span> - \n<span class='yourSearchTags' macro='foundTiddler field tags 50'/></span>\n<span macro=\"yourSearch if previewText\"><div class='yourSearchText' macro='foundTiddler field text 250'/></div></span>\n<!--\n}}}\n-->";config.shadowTiddlers.YourSearch="<<tiddler [[YourSearch Help]]>>";config.shadowTiddlers["YourSearch Result"]="The popup-like window displaying the result of a YourSearch query.";config.macros.search.handler=myMacroSearchHandler;var checkForOtherHijacker=function(){if(config.macros.search.handler!=myMacroSearchHandler){alert("Message from YourSearchPlugin:\n\n\nAnother plugin has disabled the 'Your Search' features.\n\n\nYou may disable the other plugin or change the load order of \nthe plugins (by changing the names of the tiddlers)\nto enable the 'Your Search' features.")}};setTimeout(checkForOtherHijacker,5000);abego.YourSearch.getStandardRankFunction=function(){return standardRankFunction};abego.YourSearch.getRankFunction=function(){return abego.YourSearch.getStandardRankFunction()};abego.YourSearch.getCurrentTiddler=function(){return currentTiddler};abego.YourSearch.closeResult=function(){closeResult()};abego.YourSearch.getFoundTiddlers=function(){return lastResults};abego.YourSearch.getQuery=function(){return lastQuery};abego.YourSearch.onShowResult=function(useOldResult){highlightHack=lastQuery?lastQuery.getMarkRegExp():null;if(!useOldResult){pager.setItems(getLastResults())}if(!resultElement){resultElement=createTiddlyElement(document.body,"div",yourSearchResultID,"yourSearchResult")}else{if(resultElement.parentNode!=document.body){document.body.appendChild(resultElement)}}refreshResult();highlightHack=null}})()};
//%/
<!--{{{-->
<!-- <div class='header' role='banner' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'>
<div class='headerShadow'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
<div class='headerForeground'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
</div>
<div id='mainMenu' role='navigation' refresh='content' tiddler='MainMenu'></div>
<div id='sidebar'>
<div id='sidebarOptions' role='navigation' refresh='content' tiddler='SideBarOptions'></div>
<div id='sidebarTabs' role='complementary' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea' role='main'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
-->
<!--}}}-->
|PageTemplate|WebPageTemplate|
|ViewTemplate|WebViewTemplate|
|StyleSheet|WebStyleSheet|
<!--{{{-->
<div id='displayArea' role='main'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<!--<div id='footer' refresh='content' tiddler='SiteFooter'></div>-->
<!--}}}-->
<!--{{{-->
<div class='toolbar' role='navigation' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
<div class='tagging' macro='tagging'></div>
<div class='tagged' macro='tags'></div>
<div class='viewer' macro='view text wikified'></div>
<div class='tagClear'></div>
<!--}}}-->
<!--{{{-->
<link rel='stylesheet' type='text/css' href='css/bootstrap.min.css' />
<link rel='stylesheet' type='text/css' href='css/course.css' />
<div id="navigation">
<div class="container">
<div class="row">
<div class="col-md-4 hidden-xs hidden-sm"><a href="http://childrenscience.ru/"><img src="images/logo_small.svg?crc=140944996" width="176" height="25" /></a></div>
<div class="col-md-3 col-xs-6"><a href="http://childrenscience.ru/courses.html" class="nav-link">Курсы</a></div>
<div class="col-md-3 col-xs-6"><a href="http://childrenscience.ru/about.html" class="nav-link">О проекте</a></div>
</div>
</div>
</div>
<div class="container" id="main_content">
<div class="row">
<div class="col-md-12 col-xs-12" id="course-header">
<h2 id="course-name">Решение генетических задач</h2>
<h4 id="course-author">Екатерина Романовна Черткова</h4>
<h4 id="course-audience">9–11 класс</h4>
<img src="images/cpm.png" width="70px" height="67px" class="pull-left" id="course-sponsor-img" />
<div class="pull-left center-block" id="course-sponsor-txt">Курс создан при поддержке<br/>Центра Педагогического мастерства</div>
</div>
</div>
<div macro='fillCourseHeader'></div> <!-- TW stuff -->
<div class="row row-flex" id="course-content">
<div class="col-sm-4 col-xs-12" id="lessons-list">
<div class="lesson">
<a href="#" class="lesson-name h4">О курсе</a>
</div>
<div class="lesson">
<div class="lesson-name h4">Урок с подтемами или часть.</div>
<div class="sub-lessons">
<a href="#" class="lesson-name">Урок 0</a>
<a href="#" class="lesson-name lesson-active">Урок 1 (активен)</a>
<a href="#" class="lesson-name">Урок 2. С очень длинным, просто отвратительным названием.</a>
<a href="#" class="lesson-name">Урок 3</a>
</div>
</div>
<div class="lesson">
<div class="lesson-name h4">Урок с подтемами или часть.</div>
<div class="sub-lessons">
<a href="#" class="lesson-name">Урок 0</a>
<a href="#" class="lesson-name">Урок 1</a>
<a href="#" class="lesson-name">Урок 2. С очень длинным, просто отвратительным названием.</a>
<a href="#" class="lesson-name">Урок 3</a>
</div>
</div>
<div class="lesson">
<a href="#" class="lesson-name h4">Урок без подтем</a>
</div>
<div class="lesson">
<a href="#" class="lesson-name h4">Урок без подтем</a>
</div>
<div class="lesson">
<a href="#" class="lesson-name h4 lesson-active">Активный урок без подтем</a>
</div>
<div class="lesson">
<a href="#" class="lesson-name h4">Урок без подтем</a>
</div>
</div>
<div class="col-sm-8" id="lesson-content">
<h2>Строение нуклеиновых кислот</h2>
<h5>Занятие 2</h5>
<p>Курс «Решение генетических задач» поможет разобраться в алгоритме решения генетических задач, встречающихся на ЕГЭ и олимпиадах.</p>
<p>На первом занятии автор курса, преподаватель школы «Интеллектуал» Екатерина Черткова расскажет о строении нуклеиновых кислот и процессах, которые с ними происходят.</p>
<a class="btn btn-primary" href="#">Пройти занятие</a>
<ol id="lesson-steps">
<li>
<p><a href="#"><img src="images/video.svg" width="24px" height="24px" class="pull-left"/><span class="lesson-label">Шаг 1 - видео</span></a><br/><small>видео - 5:99</small></p>
</li>
<li>
<p><a href="#"><img src="images/text.svg" width="24px" height="24px" class="pull-left"/><span class="lesson-label">Шаг 2 - конспект</span></a><br/><small>конспект</small></p>
</li>
<li>
<p><a href="#"><img src="images/test.svg" width="24px" height="24px" class="pull-left"/><span class="lesson-label">Шаг 3 - тест</span></a><br/><small>текст</small></p>
</li>
<li>
<p><a href="#"><img src="images/task.svg" width="24px" height="24px" class="pull-left"/><span class="lesson-label">Шаг 4 - задание</span></a><br/><small>задание</small></p>
</li>
</ol>
<p><a href="#"><img src="images/dwnld_solid.svg" width="24px" height="24px" />Конспект лекции <img src="images/pdf.png" width="28px" height="18px" /> 278 kB </a></p>
<p><a href="#"><img src="images/dwnld_solid.svg" width="24px" height="24px" />Слайды видеолекции <img src="images/pdf.png" width="28px" height="18px" /> 278 kB </a></p>
</div>
</div>
<div macro='fillCourseContents'></div> <!-- TW stuff -->
</div>
<div id="footer">
<div class="container">
<div class=row>
<div class="col-md-8 col-sm-6">
<h4><strong>Пишите!</strong></h4>
<h4><a href="mailto:info@childrenscience.ru">info@childrenscience.ru</a></h4>
</div>
<div class="col-md-4 col-sm-6">
<h4><strong>Мы в соцсетях:</strong></h4>
<div class="row">
<div class="col-sm-3 col-xs-2"><a href="http://vk.com/childrenscience"><img src="images/vk.png" width="46px" height="46px"></a></div>
<div class="col-sm-3 col-xs-2"><a href="https://www.facebook.com/detinauka"><img src="images/fb.png" width="46px" height="46px"></a></div>
<div class="col-sm-3 col-xs-2"><a href="http://childrenscience.livejournal.com/"><img src="images/lj.png" width="46px" height="46px"></a></div>
<div class="col-sm-3 col-xs-2"><a href="https://www.youtube.com/childrenscience"><img src="images/yt.png" width="46px" height="46px"></a></div>
</div>
</div>
</div>
</div>
</div>
<script src="js/bootstrap.min.js"></script>
<!--}}}-->
<!--{{{-->
<div class='centralAllColomn'>
<div macro='unitStep'>
</div><div class='unitStepFooter' macro='unitStepFooter'>
</div> <!-- implement this, make sure it's visible and on bottom if possible -->
</div>
<!--}}}-->
[[StyleSheet]]
/*{{{*/
/* indicate visitor mode */
body { background-color: #ffffff; }
.tiddler {
padding: 0;
/*display: flex;
flex-direction: column;
min-height: 100vh;*/
}
*[tiddlyLink] { cursor: pointer }
/* .tiddlyLink { cursor: pointer } */
a:hover {
background-color: inherit;
}
a { cursor: pointer; }
h2 {
border: none;
}
#displayArea { position: relative; width: 100%; margin-left: 0; margin-right: 0; margin-top: 0; }
/* links' style adjustments */
#navigation a { padding: 25px 0 2px !important; }
#navigation .nav-link { border-bottom: thin solid rgba(0,0,0,0.2); }
#navigation .nav-link:hover { background-color: white !important; color: #00d5e0; border-bottom: 1px solid rgba(0,213,224,0.3);}
/* lesson */
.unitStep {
width: 100%;
}
.lessonNavigation {
padding-top: 9px;
padding-bottom: 5px;
border-bottom: 2px solid rgb(233,233,233);
}
.lessonNavigation a {
display: inline-block;
vertical-align: middle;
color: #198C99;
font-family: open-sans, sans-serif; font-weight: normal; font-size: 16px;
margin-left: 0.55em; margin-right:0.55em;
padding-left: 0.2em; padding-right: 0.2em;
cursor: pointer;
}
.lessonNavigation .currentStep {
padding-top: 0.2em; /*padding-bottom: 0.2em;*/
background-color: #AEF0FF; border-radius: 50%;
}
.lessonNavigation .currentStep:hover {
background-color: #AEF0FF;
}
.lessonNavigation .noLinkToContents { padding-left: 3em; }
.lessonNavigation .noLinkToContents,
.lessonNavigation .noLink { cursor: default; }
/*.lessonNavigation .noLinkToContents:hover { color: black; }*/
.lessonNavigation div { display: inline-block; }
/* the magic to enable vertical align (see http://christopheraue.net/2014/02/20/centering-with-vertical-align-middle/) */
.lessonNavigation { height: 30px; }
.lessonNavigation a:after { vertical-align: middle; display: inline-block; height: 100%; content: ''; }
.lessonNavigation a:hover { background-color: white; color: #198C99; }
.currentStep div { border-radius: 50%; background-color: #AEF0FF; }
.unitStep {
margin-top: 10px; margin-bottom: 10px; /* temporal solution until this is set by the plugin */
}
.goPrevContainer, .unitStepContentContainer, .goNextContainer {
display: inline-block;
content: ""; vertical-align: middle; /* trick from http://codepen.io/edge0703/pen/iHJuA */
}
.goPrevContainer, .goNextContainer {
width: calc(50% - 512px);/* calc version behaves bad on zoom in *//*15%;*/
height: 100%;
text-align: center;
line-height: 1em;
}
.goPrevContainer:hover, .goNextContainer:hover {
margin-top: calc(1em + 8px);
}
.goPrevContainer:hover:after {
content: "Назад"; display: block; margin-right: 4px;
margin-top: 8px;
}
.goNextContainer:hover:after {
content: "Дальше"; display: block; margin-left: 4px;
margin-top: 8px;
}
.goPrevContainer > div, .goNextContainer > div {
display: inline-block;
width: 80px; height: 80px;
}
.backContainer, .forthContainer { cursor: pointer; }
.backward, .forward {
margin-top: 16px;
display: inline-block;
/* vertical-align: middle;*/
}
.backward { margin-right: 4px; }
.forward { margin-left: 4px; }
/*.backward:before, .forward:before {
display: inline-block; height: 100%;
content: ''; vertical-align: middle;
}*/
.goPrevContainer > div:hover, .goNextContainer > div:hover {
background-color: #eeeeee;
border-radius: 50%;
}
.unitStepContentContainer {
width: 1024px;/*70%;*/
}
.contentIframe, .contentImage {
width: 100%;
}
.unitStepFooter {
border-top: 2px solid rgb(233,233,233);
}
/* course */
.courseTopLineMenu, .courseHeader, .courseTableOfContents { padding-left: 40px; }
.courseTopLineMenu {
min-height: 70px;
}
.courseTopLineMenu a:hover {
background-color: inherit;
}
.courseTopLineMenu .svg {
height: 27px;
display: inline-block;
/* vertical-align: middle;*/
}
/*.courseTopLineMenu .svg:after { vertical-align: middle; display: inline-block; height: 100%; content: ''; }*/
.courseHeader {
background-color: #77cc77; color: white;
background-repeat: no-repeat;
background-size: cover;
}
#course-header { background-size: cover !important; }
.courseTitle { font-size: 3em; }
.courseContent {}
.courseTableOfContents {
display: table-cell; width: 25%;
min-height: 1em; background-color: #dddddd;
}
.lesson-name {
cursor: pointer;
}
/* indent-0 inherits padding-left from .lesson-name */
/* .indent-1 { padding-left: 45px !important; } */
.indent-2 { padding-left: 60px !important; }
.h3, .h2 {
cursor: default; /* this is until we implement accordeon */
}
.h3:hover, .h2:hover {
text-decoration: none; /* this is until we implement accordeon */
}
.mute, .mute:hover {
color: #999999 !important;
cursor: default;
}
.courseLessonTableOfContents {
display: table-cell; width: 75%;
min-height: 1em;
padding-top: 100px;
padding-left: 50px;
}
.lessonTitle {
font-size: 3em;
}
.startLessonButton {
display: inline-block;
padding: 0.7em 1em;
margin: 1em 0;
background-color: #1F98AE;
color: white;
border-radius: 0.5em;
}
.chem_page_preview { width:50%; }
.startLessonButton:hover {
background-color: #006F82;
}
.lessonStepInCourseContents {
height: 45px;
color: rgb(123, 133, 135);
border-bottom: 1px solid rgb(222, 222, 222);
}
.lessonStepInCourseContents div {
display: inline-block;
vertical-align: middle;
}
.lessonStepInCourseContents div:before {
display: inline-block; height: 100%; vertical-align: middle; content: '';
}
.lessonStepNumber {
width: 2em;
text-align: center;
font-weight: bold;
}
.attachment-icon { width: 39px; height: 24px; }
.pdf-attachment-icon { width: 37px; height: 18px; }
div[tiddler="Окружающий мир. 1й класс"] #lessons-list div:last-child {
margin-top: 25px;
}
div[tiddler="Окружающий мир. 1й класс"] #lessons-list div:last-child a {
border-bottom: none;
}
div[tiddler="Окружающий мир. 1й класс"] #lessons-list div:last-child a:hover {
border-bottom: none;
}
#lesson-content table {
font-size: 18px; /* for paragraphs is set inside course.css */
margin-bottom: 0.5em;
}
#lesson-content table td {
padding-bottom: 0.5em;
padding-right: 1em;
vertical-align: top;
}
#lesson-content table.logos td {
vertical-align: middle;
}
.courseFooter {
min-height: 1em; background-color: black; color: white;
}
/*}}}*/
chkAnimate: false
chkAutoSave: true
chkSaveBackups: false
/*{{{*/
/* indicate author mode */
body { background-color: #f6fff0; }
/* smartphones and tablets */
/* smartphones */
@media screen and (max-width: 700px) {
.header { display: none; }
}
/**/
div[tags~="systemConfig"] .viewer pre { clear: both !important; }
div[tags~="unit"] .tagging,
div[tags~="course"] .tagging { display: none; }
/* arrows, 80:140 */
.backward, .forward {
display: inline-block;
/* width: 64px;
height: 112px;
*/ width: 25px;
height: 48px;
}
.backward {
/* background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTIuNSAyMyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTIuNSAyMzsiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+LnN0MHtmaWxsOiMyMTJCMkI7fTwvc3R5bGU+PGc+PHBhdGggY2xhc3M9InN0MCIgZD0iTTExLjEsMjNsMS40LTEuNEwyLjQsMTEuNUwxMi41LDEuNEwxMS4xLDBMMC4zLDEwLjhDMC4xLDExLDAsMTEuMiwwLDExLjVzMC4xLDAuNSwwLjMsMC43TDExLjEsMjN6Ii8+PC9nPjwvc3ZnPg==); */
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAwCAMAAADTlCGvAAAAG1BMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4Gco9AAAACHRSTlMAEDBQwNDg8PRX3M8AAAAySURBVDjL7cmxDQAwCAOwAKXw/8VIncnSOV4NrDxAojpYpCkUz2WB0+X7WKpUH8UG5hgU7gQbPMr73AAAAABJRU5ErkJggg==);
background-repeat: no-repeat;
}
.forward {
/* background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTIuNSAyMyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTIuNSAyMzsiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+LnN0MHtmaWxsOiMyMTJCMkI7fTwvc3R5bGU+PGc+PHBhdGggY2xhc3M9InN0MCIgZD0iTTEuNCwyM0wwLDIxLjZsMTAuMS0xMC4xTDAsMS40TDEuNCwwbDEwLjgsMTAuOGMwLjIsMC4yLDAuMywwLjQsMC4zLDAuN3MtMC4xLDAuNS0wLjMsMC43TDEuNCwyM3oiLz48L2c+PC9zdmc+); */
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAwCAMAAADTlCGvAAAAG1BMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4Gco9AAAACHRSTlMAEDBQwNDg8PRX3M8AAAAySURBVDjL7ckxEgAgCAMwCiL8/8V6znRxbtaYOWzmlWDTKtVHrXmQvVmUKxRPsLhF4gD0rwQb4xAyFgAAAABJRU5ErkJggg==);
background-repeat: no-repeat;
}
.logo-sign {
background-image: url(./images/int_logo.gif);background-size: auto 30px; width:42px; height:30px;
background-repeat: no-repeat;
}
/* step types */
.pager {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0i0KHQu9C+0LlfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjQgMjQiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxnPjxnPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzM5NEI0RCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIGQ9Ik0xOS41LDIwLjVjMCwxLjEtMC45LDItMiwyaC0xMWMtMS4xLDAtMi0wLjktMi0ydi0xNGMwLTEuMSwwLjktMiwyLTJoMTFjMS4xLDAsMiwwLjksMiwyVjIwLjV6Ii8+PC9nPjxnPjxsaW5lIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzM5NEI0RCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHgxPSI4IiB5MT0iOS41IiB4Mj0iMTYiIHkyPSI5LjUiLz48L2c+PGc+PGxpbmUgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMzk0QjREIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgeDE9IjgiIHkxPSIxNy41IiB4Mj0iMTYiIHkyPSIxNy41Ii8+PC9nPjxnPjxsaW5lIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzM5NEI0RCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHgxPSI4IiB5MT0iMTMuNSIgeDI9IjE2IiB5Mj0iMTMuNSIvPjwvZz48L2c+PC9nPjwvc3ZnPg==);
}
.video {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0i0KHQu9C+0LlfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjQgMjQiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzOTRCNEQiIGQ9Ik02Ljk4LDUuNzljMC4wNDMsMC4wMTUsMC4xNjUsMC4wNDEsMC4yMzcsMC4wODVsMTEuNzU2LDcuMTM2YzAuMTQ2LDAuMDg5LDAuMjI0LDAuMTY4LDAuMjQsMC4xNjhjMC4wMDIsMCwwLjAxMS0wLjAwMSwwLjAxMi0wLjAwM2MtMC4wMDgsMC4wMjItMC4wNjIsMC4xMDgtMC4yMjMsMC4yMDZMNy4yNzksMjAuNTE5Yy0wLjA3MiwwLjA0NC0wLjI1OCwwLjA3LTAuMzAxLDAuMDg1Yy0wLjAwOS0wLjA0Ni0wLjE0Ni0wLjEwOS0wLjE0Ni0wLjE5MVY1Ljk4MUM2LjgzMyw1Ljg5OSw2Ljk3MSw1LjgzNiw2Ljk4LDUuNzkgTTYuODg4LDQuNzY4Yy0wLjU0OCwwLTEuMDU1LDAuNDQzLTEuMDU1LDEuMjEzdjE0LjQzMmMwLDAuNzcsMC41MDcsMS4yMTMsMS4wNTUsMS4yMTNjMC4yMzUsMCwwLjU2Ni0wLjA4MSwwLjg0OC0wLjI1M2wxMS43NTYtNy4xMzZjMC45NC0wLjU3MiwwLjk1Ni0xLjUwOCwwLjAxNi0yLjA4TDcuNzI4LDUuMDJDNy40NDcsNC44NDksNy4xMjMsNC43NjgsNi44ODgsNC43NjhMNi44ODgsNC43Njh6Ii8+PC9nPjwvc3ZnPg==);
}
.task {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0i0KHQu9C+0LlfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjQgMjQiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzM5NEI0RCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIGQ9Ik0xMC42NjYsMjEuNWMtMC41NSwwLTEuNDUsMC0yLDBINS43M2MtMC41NSwwLTEuMjMtMC4yMDItMS4yMy0wLjc1MlY4Ljg4OUM0LjUsOC4zMzksNS4xOCw3LjUsNS43Myw3LjVoMi4yNzhjMC41NSwwLDEuNDIzLDAuMDQxLDEuOTQtMC4xNDdjMCwwLDEuMzItMC4zODMsMC41NDQtMS4xOUM5Ljg5Myw1LjU0LDkuNTg5LDUuMDE1LDkuNTg5LDQuMTMyYzAtMS4yMDQsMS4zOTYtMi42NTYsMi43MTEtMi42NTZjMS4zMTYsMCwyLjcxMSwxLjQ4OCwyLjcxMSwyLjY5M2MwLDAuODg0LTAuMzA0LDEuNDYzLTAuOTAzLDIuMDg2Yy0wLjc3NiwwLjgwNywwLjU0NCwxLjA5NiwwLjU0NCwxLjA5NmMwLjUxNywwLjE4OCwxLjM5LDAuMTUsMS45NCwwLjE1aDIuMjc4YzAuNTUsMCwwLjYzLDAuODM5LDAuNjMsMS4zODl2MS44NmMwLDAuNTUsMC4wMjcsMS40MjEtMC4xNjUsMS45MzdjMCwwLTAuNDU1LDEuNDYyLTEuMzM2LDAuNzUxYy0wLjY4LTAuNTQ4LTEuMjYxLTEuMTI4LTIuMjI2LTEuMTI4Yy0xLjMxNiwwLTIuNzI1LDEuMzA1LTIuNzI1LDIuNTA5YzAsMS4yMDQsMS40NDQsMi41MDksMi43NiwyLjUwOWMwLjk2NSwwLDEuNTk4LTAuNTc5LDIuMjc4LTEuMTI4YzAuODgyLTAuNzExLDEuMjQ3LDAuNzUxLDEuMjQ3LDAuNzUxYzAuMTkzLDAuNTE1LDAuMTY4LDEuMzg3LDAuMTY4LDEuOTM3djEuODZjMCwwLjU1LTAuMDgsMC43NTItMC42MywwLjc1MmgtMi45MzZjLTAuNTUsMC0xLjQ1LDAtMiwwSDEwLjY2NnoiLz48L2c+PC9zdmc+);
}
.test {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0i0KHQu9C+0LlfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjQgMjQiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxnPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzM5NEI0RCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIGQ9Ik0xOS4zMzMsMTkuNWMwLDEuMTA1LTAuODk1LDItMiwyaC0xMWMtMS4xMDUsMC0yLTAuODk1LTItMnYtMTFjMC0xLjEwNSwwLjg5NS0yLDItMmgxMWMxLjEwNSwwLDIsMC44OTUsMiwyVjE5LjV6Ii8+PC9nPjxwb2x5bGluZSBmaWxsPSJub25lIiBzdHJva2U9IiMzOTRCNEQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBwb2ludHM9IjcuMDM1LDExLjIwNSAxMC43MTcsMTcuODkzIDIwLjI0OSwyLjE4OCAiLz48L2c+PC9zdmc+);
}
.pager, .video, .test, .task {
height:24px; width:24px;
background-repeat: no-repeat;
}
/*}}}*/
/***
<html>
<div class="explanation" style=""></div><div class="pager" style=""></div>
<div class="video" style=""></div>
<div class="task" style=""></div>
<div class="test" style=""></div>
</html>
***/
div[tiddler="Морфология растений"] #navigation, div[tiddler="Морфология растений"] #footer { display: none !important; }
<!--{{{-->
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml' />
<link rel="shortcut icon" href="http://childrenscience.ru/images/favicon.ico"/>
<!--}}}-->