Welcome to TiddlyWiki created by Jeremy Ruston; Copyright © 2004-2007 Jeremy Ruston, Copyright © 2007-2011 UnaMesa Association
/***
!!!608/609/610 toolbars - toggles, separators and transclusion
***/
// // {{groupbox small{
/***
http://trac.tiddlywiki.org/ticket/608 - OPEN (more/less toggle)
http://trac.tiddlywiki.org/ticket/609 - OPEN (separators)
http://trac.tiddlywiki.org/ticket/610 - OPEN (wikify tiddler/slice/section content)
This combination tweak extends the """<<toolbar>>""" macro to add use of '<' to insert a 'less' menu command (the opposite of '>' == 'more'), as well as use of '*' to insert linebreaks and "!" to insert a vertical line separator between toolbar items. In addition, this tweak add the ability to use references to tiddlernames, slices, or sections and render their content inline within the toolbar, allowing easy creation of new toolbar commands using TW content (such as macros, links, inline scripts, etc.)
To produce a one-line style, with "less" at the end, use
| ViewToolbar| foo bar baz > yabba dabba doo < |
or to use a two-line style with more/less toggle:
| ViewToolbar| foo bar baz > < * yabba dabba doo |
***/
//{{{
merge(config.macros.toolbar,{
moreLabel: 'more\u25BC',
morePrompt: 'Show additional commands',
lessLabel: '\u25C4less',
lessPrompt: 'Hide additional commands',
separator: '|'
});
config.macros.toolbar.onClickMore = function(ev) {
var e = this.nextSibling;
e.style.display = 'inline'; // show menu
this.style.display = 'none'; // hide button
return false;
};
config.macros.toolbar.onClickLess = function(ev) {
var e = this.parentNode;
var m = e.previousSibling;
e.style.display = 'none'; // hide menu
m.style.display = 'inline'; // show button
return false;
};
config.macros.toolbar.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
for(var t=0; t<params.length; t++) {
var c = params[t];
switch(c) {
case '!': // ELS - SEPARATOR (added)
createTiddlyText(place,this.separator);
break;
case '*': // ELS - LINEBREAK (added)
createTiddlyElement(place,'BR');
break;
case '<': // ELS - LESS COMMAND (added)
var btn = createTiddlyButton(place,
this.lessLabel,this.lessPrompt,config.macros.toolbar.onClickLess,'moreCommand');
break;
case '>':
var btn = createTiddlyButton(place,
this.moreLabel,this.morePrompt,config.macros.toolbar.onClickMore,'moreCommand');
var e = createTiddlyElement(place,'span',null,'moreCommand');
e.style.display = 'none';
place = e;
break;
default:
var theClass = '';
switch(c.substr(0,1)) {
case '+':
theClass = 'defaultCommand';
c = c.substr(1);
break;
case '-':
theClass = 'cancelCommand';
c = c.substr(1);
break;
}
if(c in config.commands)
this.createCommand(place,c,tiddler,theClass);
else { // ELS - WIKIFY TIDDLER/SLICE/SECTION (added)
if (c.substr(0,1)=='~') c=c.substr(1); // ignore leading ~
var txt=store.getTiddlerText(c);
if (txt) {
// trim any leading/trailing newlines
txt=txt.replace(/^\n*/,'').replace(/\n*$/,'');
// trim PRE format wrapper if any
txt=txt.replace(/^\{\{\{\n/,'').replace(/\n\}\}\}$/,'');
// render content into toolbar
wikify(txt,createTiddlyElement(place,'span'),null,tiddler);
}
} // ELS - end WIKIFY CONTENT
break;
}
}
};
//}}}
/***
|Name|[[EditSectionPlugin]]|
|Source|http://www.TiddlyTools.com/#EditSectionPlugin|
|Documentation|http://www.TiddlyTools.com/#EditSectionPlugin|
|Version|1.8.1|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|invoke popup 'section editor' for specified section of a tiddler|
!!!!!Usage
<<<
{{{
<<editSection TiddlerName##sectionname label tooltip>>
}}}
This macro adds a command link that invokes a popup editor for a specific section, where:
*''~TiddlerName##sectionname'' specifies the tiddler and section you want to edit.
**If you omit the "##sectionname" portion (i.e., only enter "~TiddlerName"), the entire content of the indicated tiddler is edited.
**If you omit the "~TiddlerName" portion (i.e., only enter "##sectionname"), the current containing tiddler (if any) is assumed.
**Changing the section name in the popup editor //renames// that section within the tiddler.
**Changing the tiddler title in the popup editor //copies// that section to another tiddler.
**If the indicated tiddler and/or section does not yet exist, it will be created when you press 'save'.
*''label'' and ''tooltip'' (both //optional//) specify the link label and mouseover help text for the 'edit section' command link.
You can also add the following macro, //at the end of a tiddler//, to automatically add an 'edit section' command link for each section shown in the tiddler.
{{{
<<editSections label tooltip>>
}}}
*''label'' and ''tooltip'' (both //optional//) specify the link label and mouseover help text for the 'edit section' command link.
>//Note: when a document is viewed in 'readOnly' mode, both of these macros are bypassed and no output is produced.//
<<<
!!!!!Sample
<<<
This is an example section for you to try
<<<
!!!!!Example
<<<
{{{
<<editSection ##Sample>>
}}}
<<editSection ##Sample>>
<<<
!!!!!Configuration
<<<
To customize and/or translate the HTML form layout used to render the section editor, edit the [[EditSectionTemplate]] shadow tiddler.
<<<
!!!!!Revisions
<<<
2012.01.29 1.8.1 invoke autoSaveChanges() when tiddlers are modified.
2011.12.22 1.8.0 added {{{<<editSections>>}}} macro for automatic adding of 'edit section...' links to headings
2011.12.20 1.7.0 added drag handling for editor panels
2011.10.28 1.6.8 fixed getMX()/getMY() for Chrome scroll offset handling
2011.09.02 1.6.7 more refactoring and cleanup of default form init/save handlers
2011.08.02 1.6.6 major code refactor to allow customization of form handling for type-specific [[PasteUpHelperPlugin]] extensions
2011.07.30 1.6.5 in removePanel(), call Popup.remove() so 'child' popups are closed when panel is closed
2011.07.24 1.6.4 refactored save() to provide updateTiddler() entry point for use with PasteUpHelperPlugin 'quickmenu' commands. Added getMX() and getMY() for cross-browser mouse coordinates
2011.06.05 1.6.3 added TiddlySpace cloneFields() to delete and save handlers so editing sections automatically copies/owns an included tiddler
2011.05.05 1.6.2 renamed delete() to deleteSection() to avoid javascript keyword errors
2011.05.04 1.6.1 in delete(), use removeTiddler() for proper notification handling
2011.05.01 1.6.0 added delete() functionality
2011.01.09 1.5.1 in handler(), don't render command link if document is readOnly
2010.12.24 1.5.0 replace use of core Popups with custom panel handling (bypass core interactions and errors)
2010.11.07 1.4.0 in popup(), render HTML form from EditSectionTemplate, and then applyHtmlMacros() to render wiki-syntax macro-generated form elements (e.g., {{{<<option>>, <<select>>}}}, etc.). Also, refactored form init/save handling to add customization 'hooks'
2010.10.25 1.3.0 added support for editing complete tiddlers using "~TiddlerName" syntax (omit "##sectionname")
2010.07.15 1.2.0 added confirmation when section rename will overwrite other section
2010.07.13 1.1.1 added 'dirty flag' confirmation to popup handling to avoid discarding unsaved changes
2010.07.11 1.1.0 fixed handling for creating new sections in existing tiddlers. Copied StickyPopupPlugin to eliminate dependency. Added Popup.showHere()
2010.05.24 1.0.2 in save(), escape regexp chars in section titles (e.g. "!SectionNameWith?InIt")
2009.09.07 1.0.1 documentation/code cleanup
2009.09.01 1.0.0 initial release
<<<
!!!!!Code
***/
//{{{
version.extensions.EditSectionPlugin= {major: 1, minor: 8, revision: 1, date: new Date(2012,1,29)};
config.macros.editSection = {
label: 'edit section...',
tip: 'edit %0',
sectionfmt: '{{hidden{\n!%0\n%1\n!end\n}}}',
sectionerr: 'Invalid tiddler/section name: %0',
newtiddlertxt: '',
discardmsg: 'Discard unsaved changes for %0?',
deletemsg: 'Are you sure you want to delete %0?',
overwritemsg: '%0##%2 already exists. Choose OK to overwrite it',
template: 'EditSectionTemplate', // DEFAULT FORM TEMPLATE
//}}}
// // PLUGIN INITIALIZATION
//{{{
init: function() {
// SHADOW PAYLOAD FOR DEFAULT EditSectionTemplate FORM DEFINITION
config.shadowTiddlers[this.template]
=store.getTiddlerText('EditSectionPlugin##HTML','');
// CLOSE PANELS IF CLICK on other than POPUP OR EDITOR PANEL
addEvent(document,'click',function(ev) {
var p=resolveTarget(ev||window.event);
while (p) {
if (hasClass(p,'editSectionPanel')) break;
if (hasClass(p,'popup')) break;
p=p.parentNode;
}
if (!p) config.macros.editSection.removeAllPanels();
return true;
});
// HIJACK QuickEditPlugin's getField() to support use with editSectionPanel
if (config.quickEdit) {
config.quickEdit.getTiddlerField=config.quickEdit.getField;
config.quickEdit.getField=function(where) {
var e=where; while(e) { if (hasClass(e,'editSectionPanel')) break; e=e.parentNode; }
return e?e.getElementsByTagName('textarea')[0]:this.getTiddlerField(where);
}
}
},
//}}}
// // GENERAL UTILITIES
//{{{
ok: function(ev) { var ev=ev||window.event;
ev.cancelBubble=true;
if(ev.stopPropagation)ev.stopPropagation();
return false;
},
getMX: function(ev) { var ev=ev||window.event; // GET MOUSE X
if (config.browser.isIE) return ev.clientX+findScrollX();// IE
if (config.userAgent.indexOf('chrome')!=-1) return ev.pageX; // Chrome
if (config.browser.isSafari) return ev.pageX+findScrollX(); // Webkit
else return ev.pageX; // Firefox/other
},
getMY: function(ev) { var ev=ev||window.event; // GET MOUSE Y
if (config.browser.isIE) return ev.clientY+findScrollY();// IE
if (config.userAgent.indexOf('chrome')!=-1) return ev.pageY; // Chrome
if (config.browser.isSafari) return ev.pageY+findScrollY(); // Webkit
else return ev.pageY; // Firefox/other
},
cloneFields: function(fields) { // for TIDDLYSPACE compatibility
var f=merge({},fields); // copy object
if (f["server.workspace"]!=config.defaultCustomFields["server.workspace"]) {
f=merge(f,config.defaultCustomFields); // overwrite with defaults
f["server.permissions"] = "read, write, create, delete";
delete f["server.page.revision"];
delete f["server.title"];
delete f["server.etag"];
}
return f;
},
//}}}
// // MACRO/CLICK HANDLER
//{{{
handler: function(place,macroName,params,wikifier,paramString,tiddler) {
if (readOnly) return;
var here=story.findContainingTiddler(place);
var tid=params[0];
var label=params[1]||this.label.format([tid]);
var tip=params[2]||this.tip.format([tid]);
var btn=createTiddlyButton(place,label,tip,this.click);
btn.setAttribute('tid',tid);
},
click: function(ev,type) { // note: optional 'type' is passed in from PasteUpPluginHelper
var parts=this.getAttribute('tid').split('##');
var title=parts[0]; var section=parts[1];
var here=story.findContainingTiddler(this);
if (!title&&here) title=here.getAttribute('tiddler');
if (!title) return false;
var tid=title; if (section&§ion.length) tid=[title,section].join('##');
return config.macros.editSection.createPanel(this,ev,tid,title,section,type);
},
//}}}
// // EDITOR PANEL HANDLER
//{{{
createPanel: function(here,ev,tid,title,section,type) {
if (!this.removeAllPanels()) return this.ok(ev);
var p=createTiddlyElement(document.body,"ol",
"editSectionPanel","popup smallform editSectionPanel");
p.root=here;
p.setAttribute('dirty',null);
p.setAttribute('message',this.discardmsg.format([tid]));
p.onmousedown=this.mousedown; p.style.cursor='move'; // for panel dragging
p.innerHTML=store.getRecursiveTiddlerText(this.getForm(tid,type),'',10);
applyHtmlMacros(p,store.getTiddler(title));
var f=p.getElementsByTagName('form')[0];
f.panel=p;
f.title.value=title;
f.section.value=section||'';
f.init=this.getInitForm(tid,type);
f.save=this.getSaveForm(tid,type);
f.init(here,f,title,section,type);
this.showPanel(p,here,ev);
return this.ok(ev);
},
showPanel: function(p,here,ev) {
var x=this.getMX(ev); var y=this.getMY(ev);
var winw=findWindowWidth();
var scrollw=winw-document.body.offsetWidth;
if(p.offsetWidth>winw*0.75) p.style.width=winw*0.75 + "px";
if(x+p.offsetWidth>winw-scrollw-1) x=winw-p.offsetWidth-scrollw-1;
var s=p.style; s.left=x+'px'; s.top=y+'px'; s.display='block';
if(config.options.chkAnimate && anim) anim.startAnimating(new Scroller(p));
else window.scrollTo(0,ensureVisible(p));
},
removePanel: function(p) {
Popup.remove(); // child popup (if any) is closed when panel is closed
if (!p || p.getAttribute('dirty')!='true' || confirm(p.getAttribute('message')))
{ if (p) removeNode(p); return true; }
return false;
},
removeAllPanels: function() {
var ok=true;
jQuery('.editSectionPanel').each(function(index){
var f=this.getElementsByTagName('form')[0];
if (f.content) f.content.blur(); // force onchange (sets 'dirty' flag as needed)
ok=config.macros.editSection.removePanel(this);
return true;
});
return ok; // FALSE if panels remain
},
//}}}
// // PANEL DRAG HANDLER
//{{{
mousedown: function(ev) { ev=ev||window.event; // MOVE PANEL
var cme=config.macros.editSection; // abbrev
// ignore clickthrough from form fields, links, and images
var target=resolveTarget(ev);
if (['TEXTAREA','SELECT','INPUT','A','IMG'].contains(target.nodeName.toUpperCase()))
return true;
// GET TRACKING ELEMENT
var track=this; // if 'capture' not supported, track in element only
if (document.body.setCapture) var track=document.body; // IE
if (window.captureEvents) var track=window; // moz
if (!track.save_onmousemove) track.save_onmousemove=track.onmousemove;
if (!track.save_onmouseup) track.save_onmouseup =track.onmouseup;
if (!track.save_onkeyup) track.save_onkeyup =track.onkeyup;
track.onmousemove=cme.dragmove;
track.onmouseup =cme.dragup;
track.onkeyup =cme.dragkeyup;
// SAVE INITIAL POSITION
track.elem=this; // panel element
track.start={
X: cme.getMX(ev), Y: cme.getMY(ev), // mouse position
T: this.offsetTop, L: this.offsetLeft, // panel position
}
return cme.ok(ev);
},
dragmove: function(ev) { ev=ev||window.event; // MOVE PANEL
var cme=config.macros.editSection; // abbrev
// CAPTURE MOUSE EVENTS DURING DRAG
if (document.body.setCapture) document.body.setCapture(); // IE
if (window.captureEvents) window.captureEvents(Event.MouseMove|Event.MouseUp,true); // moz
var e=this.elem; var s=e.style;
var dX=cme.getMX(ev)-this.start.X;
var dY=cme.getMY(ev)-this.start.Y;
e.changed=e.changed||(Math.abs(dX)>1)||(Math.abs(dY)>1); // MINIMUM 2px MOVEMENT
if (!e.changed) return cme.ok(ev);
s.top =this.start.T+dY+'px';
s.left=this.start.L+dX+'px';
return cme.ok(ev);
},
dragkeyup: function(ev) { ev=ev||window.event; // STOP DRAG (ESC key)
if (ev.keyCode==27) {
var s=this.elem.style;
s.top=this.start.T+'px';
s.left=this.start.L+'px';
return this.onmouseup(ev);
}
},
dragup: function(ev) { ev=ev||window.event; // RELEASE MOUSE
var cme=config.macros.editSection; // abbrev
if (document.body.releaseCapture) document.body.releaseCapture(); // IE
if (window.releaseEvents) window.releaseEvents(Event.MouseMove|Event.MouseUp); // moz
this.onmousemove=this.save_onmousemove;
this.onmouseup =this.save_onmouseup;
this.onkeyup =this.save_onkeyup;
return cme.ok(ev);
},
//}}}
// // EDITOR FORM HANDLER
//{{{
getForm: function(tid,type) { return this.template; }, // see PasteUpHelperPlugin
getInitForm: function(tid,type) { return this.initForm; }, // see PasteUpHelperPlugin
getSaveForm: function(tid,type) { return this.saveForm; }, // see PasteUpHelperPlugin
initForm: function(here,form,title,section,type) { // SET FORM CONTENT FROM TIDDLER SECTION
var tid=title; if (section) tid=[title,section].join('##');
form.newsection.value=tid; // target for output
form.content.value=store.getTiddlerText(tid,'');
if (version.extensions.TextAreaPlugin) new window.TextAreaResizer(form.content);
},
saveForm: function(here,ev) { // GET SECTION CONTENT FROM FORM (DEFAULT=TEXT CONTENT ONLY)
var f=here.form;
// GET TARGET TITLE/SECTION
var tid=f.newsection.value;
var parts=tid.split('##');
var title=parts[0];
var section=parts[1];
var oldsection=f.section.value;
var where=f.panel.root;
if (!title) title=story.findContainingTiddler(where).getAttribute('tiddler');
if (!title) {
displayMessage(this.sectionerr.format([f.newsection.value]));
f.newsection.focus(); f.newsection.select(); return false;
}
// CHECK FOR TIDDLER OVERWRITE
if (!section && title!=f.title.value && store.tiddlerExists(title)) {
if (!confirm(config.messages.overwriteWarning.format([title])))
{ f.newsection.focus(); f.newsection.select(); return this.ok(ev); }
}
// WRITE TIDDLER CONTENT and CLOSE PANEL
this.updateTiddler(f.content.value,title,section,oldsection);
f.panel.setAttribute('dirty',null); this.removePanel(f.panel);
return this.ok(ev);
},
changed: function(here,ev) {
here.form.panel.setAttribute('dirty','true');
return this.ok(ev);
},
cancel: function(here,ev) {
this.removePanel(here.form.panel);
return this.ok(ev);
},
remove: function(here,ev) {
var f=here.form;
var title=f.title.value;
var section=f.section.value;
var tid=title; if (section.length) tid=[title,section].join('##');
var msg=this.deletemsg.format([tid]);
if (!confirm(msg)) return this.ok(ev);
this.deleteSection(title,section);
f.panel.setAttribute('dirty',null);
this.removePanel(f.panel);
return this.ok(ev);
},
//}}}
// // TIDDLER I/O
//{{{
updateTiddler: function(txt,title,section,oldsection) {
// GET (or CREATE) TIDDLER
var t=store.getTiddler(title);
var who =t&&config.options.chkForceMinorUpdate?t.modifier:config.options.txtUserName;
var when=t&&config.options.chkForceMinorUpdate?t.modified:new Date();
if (!t) {
t=new Tiddler(); t.text=store.getTiddlerText(title,'');
if (section&&!t.text.length) t.text=this.newtiddlertxt.format([title,section]);
}
// ADD/REVISE/RENAME SECTION CONTENT (if any)
if (section) {
// GET SECTION VALUES
if (!oldsection) var oldsection=section;
var oldval=store.getTiddlerText(title+'##'+oldsection); // previous section value
var newval=txt; // revised section value
var existingval=store.getTiddlerText(title+'##'+section); // existing section value
// REVISE TIDDLER TEXT
var txt=t.text; // default tiddler text = unchanged
var pattern=new RegExp('(.*!{1,6})'+oldsection.escapeRegExp()+'\\n'
+(oldval||'').escapeRegExp()+'((?:\\n!{1,6}|$).*)');
var altpattern=this.sectionfmt.format([oldsection,oldval||'']);
if (section!=oldsection && existingval) { // rename overwrites another section...
if (!confirm(this.overwritemsg.format([title,section])))
return this.ok(ev);
txt=txt.replace(altpattern,''); // REMOVE old section (auto-generated)
txt=txt.replace(pattern,'$2'); // REMOVE old section (generic format)
// TARGET new section name and value
pattern=new RegExp('(.*!{1,6})'+section.escapeRegExp()+'\\n'
+existingval.escapeRegExp()+'((?:\\n!{1,6}|$).*)');
oldval=existingval;
}
if (typeof oldval=="string") // section exists... update/rename it
txt=txt.replace(pattern,'$1'+section+'\n'+newval+'$2');
else // otherwise, append a new section to end of tiddler
txt=txt+this.sectionfmt.format([section,newval]);
}
// SAVE TIDDLER
var fields=this.cloneFields(t.fields);
store.saveTiddler(title,title,txt,who,when,t.tags,fields);
story.refreshTiddler(title,null,true);
autoSaveChanges();
},
deleteSection: function(title,section) {
// GET TIDDLER
var t=store.getTiddler(title); if (!t) return;
var who =t&&config.options.chkForceMinorUpdate?t.modifier:config.options.txtUserName;
var when=t&&config.options.chkForceMinorUpdate?t.modified:new Date();
if (!section) { // REMOVE TIDDLER
store.removeTiddler(title);
} else { // REMOVE SECTION FROM TIDDLER
var val=store.getTiddlerText(title+'##'+section); // CURRENT SECTION VALUE
if (typeof val=="string") { // section exists
var txt=t.text; // default tiddler text = unchanged
var pattern=new RegExp('(.*!{1,6})'+section.escapeRegExp()+'\\n'
+(val||'').escapeRegExp()+'((?:\\n!{1,6}|$).*)');
var altpattern=this.sectionfmt.format([section,val||'']);
txt=txt.replace(altpattern,''); // REMOVE old section (auto-generated)
txt=txt.replace(pattern,'$2'); // REMOVE old section (generic format)
var fields=this.cloneFields(t.fields);
store.saveTiddler(title,title,txt,who,when,t.tags,fields);
story.refreshTiddler(title,null,true);
autoSaveChanges();
}
}
}
}
//}}}
// // EDIT SECTIONS MACRO
//{{{
config.macros.editSections = {
label: 'edit...',
tip: 'edit this section',
command: '~~<<editSection [[##%0]] "%1" "%2">>~~',
handler: function(place,macroName,params,wikifier,paramString,tiddler) {
if (readOnly) return;
var elems=place.parentNode.getElementsByTagName("*");
for (var i=0; i<elems.length; i++) { var e=elems[i]; // for each heading element
if (!['H1','H2','H3','H4','H5'].contains(e.nodeName)) continue;
var section=e.textContent;
var label=(params[0]||this.label).format([section]);
var tip =(params[1]||this.tip ).format([section]);
wikify(this.command.format([section,label,tip]),e);
}
}
}
//}}}
/***
//{{{
!HTML
<!--{{{-->
<!--
|Name|EditSectionTemplate|
|Source||
|Version||
|Author||
|License|http://www.TiddlyTools.com/#LegalStatements|
|Type|template|
|Requires|EditSectionPlugin|
|Description|popup editor form template used by EditSectionPlugin|
-->
<form action='javascript:;' style="white-space:nowrap">
<input type="hidden" name="title" value="">
<input type="hidden" name="section" value="">
<input type="text" name="newsection" value="" autocomplete="off" style="width:61%"
onchange="return config.macros.editSection.changed(this,event);">
<input type=button value="save" style="width:12%"
onclick="return config.macros.editSection.saveForm(this,event)">
<input type=button value="cancel" style="width:12%"
onclick="return config.macros.editSection.cancel(this,event)">
<input type=button value="delete" style="width:12%"
onclick="return config.macros.editSection.remove(this,event)">
<div macro="tiddler QuickEditToolbar"></div>
<textarea name="content" rows="15" cols="80" autocomplete="off"
onchange="return config.macros.editSection.changed(this,event)"></textarea>
</form>
<!--}}}-->
!end
//}}}
***/
// //<<editSections "edit">>
<!--{{{-->
<!--
|Name|EditSectionTemplate|
|Source||
|Version||
|Author||
|License|http://www.TiddlyTools.com/#LegalStatements|
|Type|template|
|Requires|EditSectionPlugin|
|Description|popup editor form template used by EditSectionPlugin|
-->
<center> Click & drag</center>
<form action='javascript:;' style="white-space:nowrap">
<input type="hidden" name="title" value="">
<input type="hidden" name="section" value="">
<input type="text" name="newsection" value="" autocomplete="off" style="width:61%"
onchange="return config.macros.editSection.changed(this,event);">
<input type=button value="save" style="width:12%"
onclick="return config.macros.editSection.saveForm(this,event)">
<input type=button value="cancel" style="width:12%"
onclick="return config.macros.editSection.cancel(this,event)">
<input type=button value="delete" style="width:12%"
onclick="return config.macros.editSection.remove(this,event)">
<div macro="tiddler QuickEditToolbar"></div>
<textarea name="content" rows="15" cols="80" autocomplete="off"
onchange="return config.macros.editSection.changed(this,event)"></textarea>
</form>
<!--}}}-->
/***
|Name|FoldHeadingsPlugin|
|Source|http://www.TiddlyTools.com/#FoldHeadingsPlugin|
|Version|1.1.2|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|automatically turn headings into slider-like panels that can be folded/unfolded with a single click|
This plugin defines a macro that automatically converts heading-formatted content into sliders that let you expand/collapse their content by clicking on individual headings.
!!!!!Usage
<<<
{{{
<<foldHeadings opened|closed tag tag tag...>>
}}}
where: ''opened'' or ''closed'' is a keyword indicating the initial state of the sections (default: opened), and ''tag tag tag...'' is an optional list of tags to match, so that the foldable effect is only applied to tiddlers that contain one (or more) of the indicated tags.
When you place the macro in a tiddler, any heading-formatted content (i.e, "!" through "!!!!!") in that tiddler will automatically become //'fold-able'//, allowing you to expand/collapse the content that follows each heading simply by clicking on that heading. Each content section begins with the first element following a heading, and continues until either another heading is found or the end of the tiddler is reached. For example:
{{{
<<foldHeadings closed>>
}}}
is embedded in ''this'' tiddler in order to make all the headings it contains 'fold-able'. Note that the macro has been placed at the //end// of the tiddler because it only operates on *rendered* content. Thus, only headings that //precede// it in the same tiddler will become fold-able, as any headings that //follow// it are not actually rendered until //after// the macro has been processed.
You can further limit the effect of the macro within the tiddler by surrounding several headings in a "CSS class wrapper" ("""{{classname{...}}}""") or other containing DOM element (e.g., """@@display:inline;...@@""") and then embedding the {{{<<foldHeadings>>}}} macro inside that container (at the end)... only those headings that are also within that container will be made fold-able, instead of converting ''all'' the headings in that tiddler.
Conversely, if you want the fold-able ability to apply to the headings in //all// tiddlers, ''without having to alter //any// of those individual tiddlers'', you can add the macro to the end of your [[ViewTemplate]], so that it will be invoked after the content in each tiddler has been rendered, causing all headings they contain to automatically become fold-able. For example:
{{{
<span macro="foldHeadings closed"></span>
}}}
You can also limit this effect to selected tiddlers by specifying one or more tags as additional macro parameters. For example:
{{{
<span macro="foldHeadings closed systemConfig"></span>
}}}
is only applied to headings contained in //plugin tiddlers// (i.e., tiddlers tagged with <<tag systemConfig>>), while headings in other tiddlers remain unaffected by the macro, even though it is embedded in the common [[ViewTemplate]] definition.
<<<
!!!!!Revisions
<<<
2009.11.30 [1.1.2] corrected CSS 'text-weight' to 'font-weight'
2009.01.06 [1.1.1] removed hijack of scrollToSection() (see [[SectionLinksPlugin]] for equivalent code)
2008.11.17 [1.1.0] added hijack of 'scrollToSection()' function (see [[CoreTweaks]] and http://trac.tiddlywiki.org/ticket/784)
2007.12.06 [1.0.2] fix handling for empty sections when checking for sliderPanel/floatingPanel
2007.12.02 [1.0.1] fix handling when content following a heading is already a sliderPanel/floatingPanel
2007.12.01 [1.0.0] initial release
<<<
!!!!!Code
***/
//{{{
version.extensions.FoldHeadingsPlugin= {major: 1, minor: 1, revision: 2, date: new Date(2009,11,30)};
config.macros.foldHeadings = {
guideText: "opened|closed className",
showtip: "click to show '%0'",
hidetip: "click to hide '%0'",
showlabel: "more...",
hidelabel: "[x]",
html: "<span style='float:right;font-weight:normal;font-size:80%;' class='TiddlyLinkExisting'>%0 </span>",
handler: function(place,macroName,params) {
var show=params[0] && params.shift().toLowerCase()!="closed";
if (params.length) { // if filtering by tag(s)
var here=story.findContainingTiddler(place);
if (here) var tid=store.getTiddler(here.getAttribute("tiddler"));
if (!tid || !tid.tags.containsAny(params)) return; // in a tiddler and not tagged... do nothing...
}
var elems=place.parentNode.getElementsByTagName("*");
var heads=[]; for (var i=0; i<elems.length; i++) { // get non-foldable heading elements
var n=elems[i].nodeName; var foldable=hasClass(elems[i],"foldable");
if ((n=="H1"||n=="H2"||n=="H3"||n=="H4"||n=="H5")&&!foldable)
heads.push(elems[i]);
}
for (var i=0; i<heads.length; i++) { var h=heads[i]; // for each heading element...
// find start/end of section content (up to next heading or end of content)
var start=end=h.nextSibling; while (end && end.nextSibling) {
var n=end.nextSibling.nodeName.toUpperCase();
if (n=="H1"||n=="H2"||n=="H3"||n=="H4"||n=="H5") break;
end=end.nextSibling;
}
if (start && hasClass(start,"sliderPanel")||hasClass(start,"floatingPanel")) continue; // heading is already a slider!
var span=createTiddlyElement(null,"span",null,"sliderPanel"); // create container
span.style.display=show?"inline":"none"; // set initial display state
h.parentNode.insertBefore(span,start); // and insert it following the heading element
// move section elements into container...
var e=start; while (e) { var next=e.nextSibling; span.insertBefore(e,null); if (e==end) break; e=next; }
// set heading label/tip/cursor...
h.title=(show?this.hidetip:this.showtip).format([h.textContent])
h.innerHTML=this.html.format([show?this.hidelabel:this.showlabel])+h.innerHTML;
h.style.cursor='pointer';
addClass(h,"foldable"); // so we know it been done (and to add extra styles)
h.onclick=function() {
var panel=this.nextSibling; var show=panel.style.display=="none";
// update panel display state
if (config.options.chkAnimate) anim.startAnimating(new Slider(panel,show));
else panel.style.display = show?"inline":"none";
// update heading label/tip
this.removeChild(this.firstChild); // remove existing label
var fh=config.macros.foldHeadings; // abbreviation for readability...
this.title=(show?fh.hidetip:fh.showtip).format([this.textContent])
this.innerHTML=fh.html.format([show?fh.hidelabel:fh.showlabel])+this.innerHTML;
}
}
}
}
if (story.scrollToSection) {
Story.prototype.foldheadings_scrollToSection=Story.prototype.scrollToSection;
Story.prototype.scrollToSection=function(title,section) {
var e=this.foldheadings_scrollToSection.apply(this,arguments);
// if scrolling to a folded section heading, click to expand it
if (e && hasClass(e,'foldable') && e.nextSibling.style.display=='none') e.onclick();
}
}
//}}}
// //<<foldHeadings closed>>
Hi Craig
I've added:
!EditSectionPlugin
Allow editing of sections
!FoldHeadingsPlugin
Fold sectionheadings - added in the ViewTemplate for all tiddlers tagged with <<tag systemConfig>> and <<tag closed>>..
!SectionLinksPlugin
TOC and scroll section into view
!NestedSlidersPlugin
Create a button and show toc in a slider
!zzCombineEdit&Toc
Hijack Eric's SectionLinksPlugin for autogenerated TOC + editsection buttons
!MoveablePanelPlugin
Allow for detaching and hovering of the editable toc slider content
!!SliderToc
Content of the slider - style it any way you want...
- added an "scroll to top" arrow for when the toc is detached and hovered/follows page...
<<<
Tip: First detach - then click hover...
<<<
!608/609/610 toolbars - toggles, separators and transclusion
(Coretweak from tiddlytools - to add the SliderToc tiddler content to ToolbarCommands)
!Enjoy
Måns Mårtensson
/***
|''Name:''|LoadRemoteFileThroughProxy (previous LoadRemoteFileHijack)|
|''Description:''|When the TiddlyWiki file is located on the web (view over http) the content of [[SiteProxy]] tiddler is added in front of the file url. If [[SiteProxy]] does not exist "/proxy/" is added. |
|''Version:''|1.1.0|
|''Date:''|mar 17, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#LoadRemoteFileHijack|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0|
***/
//{{{
version.extensions.LoadRemoteFileThroughProxy = {
major: 1, minor: 1, revision: 0,
date: new Date("mar 17, 2007"),
source: "http://tiddlywiki.bidix.info/#LoadRemoteFileThroughProxy"};
if (!window.bidix) window.bidix = {}; // bidix namespace
if (!bidix.core) bidix.core = {};
bidix.core.loadRemoteFile = loadRemoteFile;
loadRemoteFile = function(url,callback,params)
{
if ((document.location.toString().substr(0,4) == "http") && (url.substr(0,4) == "http")){
url = store.getTiddlerText("SiteProxy", "/proxy/") + url;
}
return bidix.core.loadRemoteFile(url,callback,params);
}
//}}}
/***
|Name|[[MoveablePanelPlugin]]|
|Source|http://www.TiddlyTools.com/#MoveablePanelPlugin|
|Documentation|http://www.TiddlyTools.com/#MoveablePanelPluginInfo|
|Version|3.0.4|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|move/size any tiddler or page element|
Use the mouse to move/resize any specific tiddler content, page element, or [[floating slider panel|NestedSlidersPlugin]].
!!!!!Documentation
>see [[MoveablePanelPluginInfo]]
!!!!!Configuration
<<<
<<option chkMoveablePanelShowStatus>> show position/size while moving/resizing a panel
<<option chkMoveablePanelShowManager>> automatically add Panel Manager button to undocked panels (see [[PanelManagerPlugin]])
<<<
!!!!!Revisions
<<<
2010.12.24 3.0.4 fixed findMouseX/findMouseY for webkit browsers
|please see [[MoveablePanelPluginInfo]] for additional revision details|
2006.03.04 1.0.0 Initial public release
<<<
!!!!!Code
***/
//{{{
version.extensions.MoveablePanelPlugin= {major: 3, minor: 0, revision: 4, date: new Date(2010,12,24)};
if (config.macros.moveablePanel===undefined) config.macros.moveablePanel={};
//}}}
// // translate
//{{{
// TRANSLATORS: copy this section to MoveablePanelPluginLingoXX (where 'XX' is a language/country code)
if (config.macros.moveablePanel===undefined) config.macros.moveablePanel={};
merge(config.macros.moveablePanel,{
foldLabel: '\u2212', // minus
foldTip: 'FOLD=reduce panel size',
unfoldLabel: '+',
unfoldTip: 'UNFOLD=restore panel size',
hoverLabel: '^',
hoverTip: 'HOVER=keep panel in view when scrolling',
scrollLabel: '\u2248', // asymp
scrollTip: 'SCROLL=allow panel to move with page',
closeLabel: 'X',
closeTip: 'CLOSE=hide this panel',
dockLabel: '\u221A', // radic
dockTip: 'DOCK=reset size/position',
noPid: 'unnamed panel',
statusMsg: '%0: pos=(%1,%2)%3 size=(%4,%5) z=%6',
hoveredMsg: '[hovering]',
dockedTip: '%0: docked',
scrollMsg: '%0: pos=(%1,%2)',
msgDuration: 3000,
moveTip: '%0DRAG EDGE=move',
sizeTip: '(SHIFT=resize)',
sizeWidthTip: '(SHIFT=resize width)',
sizeHeightTip: '(SHIFT=resize height)',
clickTip: 'CLICK=bring to front, SHIFT-CLICK=send to back',
dblclickdockTip: 'DOUBLE-CLICK=dock',
dblclickunfoldTip:'DOUBLE-CLICK=unfold',
foldParam: 'fold',
hoverParam: 'hover',
nocloseParam: 'noclose',
nodockParam: 'nodock',
undockedParam: 'undocked',
jumpParam: 'jump',
dockParam: 'dock',
moveParam: 'move',
labelParam: 'label',
promptParam: 'prompt',
allParam: 'all',
nameParam: 'name',
topParam: 'top',
leftParam: 'left',
widthParam: 'width',
heightParam: 'height',
managerParam: 'manager'
});
//}}}
// // global functions (general utilities)
//{{{
// if removeCookie() function is not defined by TW core, define it here (for <TW2.5)
if (window.removeCookie===undefined) {
window.removeCookie=function(name) {
document.cookie = name+'=; expires=Thu, 01-Jan-1970 00:00:01 UTC; path=/;';
}
}
if (window.copyObject===undefined) {
window.copyObject=function(src) {
for (var i in src) this[i]=typeof src[i]!='object'?src[i]:new copyObject(src[i]);
}
}
if (window.compareObjects===undefined) {
window.compareObjects=function(a,b) {
if (a===b) return true;
if (a==undefined||b==undefined) return false;
for (var i in a) if (typeof a[i]!='object'?a[i]!==b[i]:!compareObjects(a[i],b[i])) return false;
return true;
}
}
if (window.isEmptyObject===undefined) {
window.isEmptyObject=function(src) { for (var i in src) return false; return true; }
}
// cross-browser metrics
window.findMouseX=function(ev) { if (!ev) return 0; var x=0;
if (config.browser.isIE) return ev.clientX+findScrollX();// IE
if (config.browser.isSafari) return ev.pageX+findScrollX(); // Webkit
else return ev.pageX; // Firefox/other
}
window.findMouseY=function(ev){ if (!ev) return 0; var y=0;
if (config.browser.isIE) return ev.clientY+findScrollY();// IE
if (config.browser.isSafari) return ev.pageY+findScrollY(); // Webkit
else return ev.pageY; // Firefox/other
}
//}}}
// // macro
//{{{
merge(config.macros.moveablePanel,{
handler: function(place,macroName,params,wikifier,paramString,tiddler) {
// ALTERNATIVE OUTPUT: Panel Manager macro extensions...
if (this.manager && this.manager.handler(place,macroName,params,wikifier,paramString,tiddler))
return; // processed by PanelManager
// UNPACK KEYWORD PARAMS
var showfold =params.contains(this.foldParam);
var showhover =params.contains(this.hoverParam);
var showclose =!params.contains(this.nocloseParam);
var showdock =!params.contains(this.nodockParam);
var showmanager =params.contains(this.managerParam);
var startundocked=params.contains(this.undockedParam);
var jump =params.contains(this.jumpParam);
var dock =params.contains(this.dockParam);
var move =params.contains(this.moveParam);
var all =params.contains(this.allParam);
// UNPACK VALUE PARAMS
params=paramString.parseParams('anon',null,true,false,false);
var label =getParam(params,this.labelParam,null);
var prompt=getParam(params,this.promptParam,null);
var name =getParam(params,this.nameParam,null);
var top =getParam(params,this.topParam,null);
var left =getParam(params,this.leftParam,null);
var width =getParam(params,this.widthParam,null);
var height=getParam(params,this.heightParam,null);
// COMMANDS: JUMP, MOVE, DOCK
if (jump||move||dock) {
if (!label||!label.length) {
var p=this.findPanel(name);
if (jump) { if (p) this.scrollToPanel(p,true); else window.scrollTo(left,top); }
if (move) { this.movePanel(p,left,top,true,true); }
if (dock) { if (all) this.forAllPanels(this.dockPanel); else if (p) this.dockPanel(p); }
return;
}
var tip=(jump?this.jumpParam:move?this.moveParam:dock?this.dockParam:'')+': '+name;
var b=createTiddlyButton(place,label,prompt||tip, function(ev) {
var cmm=config.macros.moveablePanel; var p=cmm.findPanel(this.name);
if (this.jump) { if (p) cmm.scrollToPanel(p,true); else window.scrollTo(this.left,this.top); }
if (this.move) { if (p) cmm.movePanel(p,this.left,this.top,true,true); }
if (this.dock) { if (p) cmm.dockPanel(p); else if (this.all) cmm.forAllPanels(cmm.dockPanel); }
return cmm.processed(ev)
},'button');
b.jump=jump; b.move=move; b.dock=dock;
b.name=name; b.all=all; b.left=left; b.top=top;
return;
}
// PANEL SETUP
var p=this.getPanel(place);
this.cachePanel(p);
addClass(p,'moveablePanel');
p.pid=name;
p.showmanager=showmanager;
p.fixedheight=height||undefined;
p.fixedwidth=width||undefined;
this.addPanelButtons(p,showfold,showhover,showclose,showdock,showmanager);
this.addMouseHandlers(p);
if (startundocked) {
this.undockPanel(p);
if (!startingUp) { this.bringPanelToFront(p); this.scrollToPanel(p); }
}
if (this.manager) { this.manager.applyMap(p); this.manager.trackMap(p); }
this.notify(p);
},
//}}}
// // notifications
//{{{
quiet: 0, // flag to suspend/resume notifications
notify: function(p) { // notify others of panel changes
if (this.quiet) return;
if (this.manager) this.manager.notify(p); // pass notices to manager (updates viewers)
},
//}}}
// // general panel utilities
//{{{
getPanel: function(place) { // find containing panel or floating slider (use current element as fallback)
var p=place;
while (p && !(hasClass(p,'moveablePanel')||hasClass(p,'floatingPanel'))) p=p.parentNode;
return p||place;
},
getAllPanels: function(zSort) { // find 'moveablePanel' elements (optionally sort by zIndex)
var panels=[];
var sortByZindex=function(a,b){
var v1=parseInt(a.style.zIndex); if (isNaN(v1)) v1=0;
var v2=parseInt(b.style.zIndex); if (isNaN(v2)) v2=0;
return(v1==v2)?0:(v1>v2?1:-1);
}
// if native browser fn is defined, use it (*much* more efficient!)
if (document.getElementsByClassName) {
var elems=document.getElementsByClassName('moveablePanel');
for (var i=0; i<elems.length; i++) panels.push(elems[i]);
return zSort?panels.sort(sortByZindex):panels;
}
// otherwise, find all DIVs and SPANs with the right class
// NOTE: IE requires use of Enumerator() to iterate over elements, or it FREEZES UP COMPLETELY!!
var isIE=config.browser.isIE;
var elems=document.getElementsByTagName('DIV');
for (var i=isIE?new Enumerator(elems):0; isIE?!i.atEnd():(i<elems.length); isIE?i.moveNext():i++) {
var panel=isIE?i.item():elems[i];
if (hasClass(panel,'moveablePanel')) panels.push(panel);
}
var elems=document.getElementsByTagName('SPAN');
for (var i=isIE?new Enumerator(elems):0; isIE?!i.atEnd():(i<elems.length); isIE?i.moveNext():i++) {
var panel=isIE?i.item():elems[i];
if (hasClass(panel,'moveablePanel')) panels.push(panel);
}
return zSort?panels.sort(sortByZindex):panels;
},
findPanel: function(pid) { // find a named panel
var p=this.getAllPanels();
for (var i=0; i<p.length; i++) { if (pid && p[i].pid==pid) return p[i]; }
return undefined;
},
forAllPanels: function(callback) { // invoke a function on each panel
var panels=this.getAllPanels();
this.quiet++;
for (var i=0; i<panels.length; i++) callback.apply(this,[panels[i]]);
this.quiet--;
this.notify('all');
},
cachePanel: function(p) { // save original styles and handlers
if (!p.saved) p.saved={
x:p.style.left||'', y:p.style.top||'', w:p.style.width||'', h:p.style.height||'',
z:p.style.zIndex||'', pos:p.style.position||'', title: p.title,
mouseover:p.onmouseover, mouseout:p.onmouseout,
mousedown:p.onmousedown, mousemove:p.onmousemove, dblclick:p.ondblclick
};
},
restorePanel: function(p) { // restore original styles
if (!p.saved) return;
p.style.left=p.saved.x; p.style.top=p.saved.y; p.style.width=p.saved.w; p.style.height=p.saved.h;
p.style.zIndex=p.saved.z; p.style.position=p.saved.pos; p.title=p.saved.title;
removeClass(p,'folded'); removeClass(p,'hover'); removeClass(p,'undocked');
},
//}}}
// // panel metrics
//{{{
getPanelOffset: function(p) { // adjustment for child elements inside relative/floatingPanel containers
var r=new Object(); r.x=0; r.y=0; if (!p) return r;
var pp=p.parentNode; while (pp && !(pp.style&&pp.style.position=='relative')) pp=pp.parentNode;
if (pp) { r.x+=findPosX(pp); r.y+=findPosY(pp); }
var pp=p.parentNode; while (pp && !hasClass(pp,'floatingPanel')) pp=pp.parentNode;
if (pp) { r.x+=findPosX(pp); r.y+=findPosY(pp); }
return r;
},
// PROBLEM: the offsetWidth/offsetHeight do not seem to account for padding or borders
// WORKAROUND: subtract padding and border (in px) from width and height
// ISSUE: I still don't understand why this is needed...
// TBD: get padding/border values from p.style and convert to px
// NOTE: 10.6667 seems to be about 1em...
getPanelEdgeWidth:
function(p) { return 10.6667; },
getPanelEdgeHeight:
function(p) { return 10.6667; },
getPanelHeight:
function(p) { var pad=10.6667; var border=1; return p.offsetHeight-(pad*2+border*2); },
getPanelWidth:
function(p) { var pad=10.6667; var border=1; return p.offsetWidth -(pad*2+border*2); },
//}}}
// // panel stacking (zIndex)
//{{{
isStackable: function(p) { // zIndex is only effective with absolute or fixed elements
return (['absolute','fixed'].contains(p.style.position)&&!hasClass(p,'popup'));
},
normalizeStack: function(panels) { // set zIndex to correspond to stack order
for (var i=0; i<panels.length; i++) {var z=panels[i].style.zIndex;
if (z==0||z=='auto') continue; // if not stacking (e.g., 'auto', '', or null)
if (z<10000 || z>10000) continue; // use large values for "always in front/back"
if (z!=i+2) panels[i].style.zIndex=i+2;
if (this.manager) this.manager.trackMap(panels[i]);
}
return panels;
},
bringPanelToFront: function(p) { if (!p) return;
if (!this.isStackable(p)) return; // can't be stacked
var panels=this.getAllPanels(true);
// WFFL - normalizing every time works, but takes too long
// if (p.style.zIndex>panels.length+2) return; // stay in front
// this.normalizeStack(panels);
// p.style.zIndex=panels.length+2;
// WFFL - for now, just bump up the max (ignore z>10000) and normalize much less often
if (p.style.zIndex>1000) this.normalizeStack(panels);
var zMax=0; if (panels.length) {
var i=panels.length-1; zMax=parseInt(panels[i].style.zIndex);
while (zMax>10000 && i>0) zMax=parseInt(panels[--i].style.zIndex);
if (p==panels[i]) return; // already in front
if (isNaN(zMax)) zMax=0;
}
p.style.zIndex=zMax+1;
this.notify(p);
},
sendPanelToBack: function(p) { if (!p) return;
if (!this.isStackable(p)) return; // can't be stacked
var panels=this.getAllPanels(true);
// WFFL - normalizing every time works, but takes too long
// if (p.style.zIndex<2) return; // stay in back
// this.normalizeStack(panels);
// p.style.zIndex=1;
// WFFL - for now, just bump down the min (ignore z<10000) and normalize much less often
if (p.style.zIndex<1000) this.normalizeStack(panels);
var zMin=0; if (panels.length) {
var i=0; zMin=parseInt(panels[i].style.zIndex);
while (zMin<10000 && i<panels.length-1) zMin=parseInt(panels[++i].style.zIndex);
if (p==panels[i]) return; // already in back
if (isNaN(zMin)) zMin=0;
}
p.style.zIndex=zMin-1;
this.notify(p);
},
returnPanelToStack: function(p) { if (!p) return;
p.style.zIndex=p.saved?p.saved.zIndex:'';
this.notify(p);
},
//}}}
// // panel scrolling
//{{{
noScrollX: 0, // flags to disable TW built-in scrolling behavior
noScrollY: 0, // set by hijacks, cleared by ensurePanelVisible(), below
// scroll view to show panel along nearest edge of window or centered (optional)
scrollToPanel: function(p,center) { if (!p) return;
if (hasClass(p,'popup')) return; // popup=let core scrolling handle it
if (hasClass(p,'hover')) return; // hover=always in view=don't scroll
var scrollSize=findWindowWidth()-document.body.offsetWidth; // width of scrollbar
var sx=findScrollX(); var ww=findWindowWidth()-scrollSize;
var sy=findScrollY(); var wh=findWindowHeight()-scrollSize;
var px=findPosX(p); var pw=p.offsetWidth;
var py=findPosY(p); var ph=p.offsetHeight;
var nx=sx; var ny=sy; // assume no scrolling is needed
// if BR is not in view, scroll to show BR
if (px+pw>sx+ww) nx=px+pw-ww;
if (py+ph>sy+wh) ny=py+ph-wh;
// if TL not in view or too big... scroll to show TL
if (px<nx || px>nx+ww || px+pw>nx+ww) nx=px;
if (py<ny || py>ny+wh || py+ph>ny+wh) ny=py;
// optionally, center in view (if panel fits)
if (center && pw<ww) nx-=(ww-pw)/2;
if (center && ph<wh) ny-=(wh-ph)/2;
if (nx!=sx||ny!=sy) { // if we need to scroll...
window.scrollTo(nx,ny);
if (config.options.chkMoveablePanelShowStatus && !startingUp) {
var id=hasClass(p,'tiddler')?p.getAttribute('tiddler'):p.pid;
this.timedMessage(this.scrollMsg.format([id||this.noPid,px,py]),this.msgDuration);
}
this.notify(p);
}
},
// bring to front and scroll into view (with optional ASYNC)
ensurePanelVisible: function(p,delay) { if (!p) return;
if (delay && !startingUp) { // wait for core animation to complete...
if (hasClass(p,'tiddler'))
p=config.macros.moveablePanel.findPanel(p.getAttribute('tiddler'))||p;
if (!p.id) p.id=new Date().getTime()+Math.random(); // unique ID
var code='config.macros.moveablePanel.ensurePanelVisible(document.getElementById("%0"));';
setTimeout(code.format([p.id]),delay);
return;
}
// unblock scrolling and bring the panel into view
if (this.noScrollX>0) this.noScrollX--; if (this.noScrollY>0) this.noScrollY--;
if (hasClass(p,'popup')) return; // leave popups alone!
this.bringPanelToFront(p);
if (this.noScrollX+this.noScrollY==0 && !startingUp) // no scroll during document startup
this.scrollToPanel(p);
},
//}}}
// // panel status
//{{{
formatPanelStatus: function(p) {
var s=p.style; var msg=this.statusMsg.format([p.pid||this.noPid,
s.left,s.top,hasClass(p,'hover')?this.hoveredMsg:'',s.width,s.height,s.zIndex]);
return msg.replace(/(\.[0-9]+)|px/g,''); // remove decimals and 'px'
},
showPanelStatus: function(p,show) { // display panel info in titlebar while moving/sizing
if (!config.options.chkMoveablePanelShowStatus) return;
if (show) document.title=this.formatPanelStatus(p)
else refreshPageTitle();
},
timedMessage: function(msg,duration) {
document.title=msg; setTimeout('refreshPageTitle()',duration);
},
getPanelTooltip: function(p) {
return hasClass(p,'undocked')?this.formatPanelStatus(p):this.dockedTip.format([p.pid||this.noPid]);
},
//}}}
// // panel actions
//{{{
undockPanel: function(p,front) { // undocked with default pos/size
if (hasClass(p,'undocked')) return; // already undocked
// get size BEFORE undocking
p.style.width=p.fixedwidth ||(this.getPanelWidth(p)+'px');
p.style.height=p.fixedheight||(this.getPanelHeight(p)+'px');
addClass(p,'undocked'); if (!this.isStackable(p)) p.style.position='absolute'; // UNDOCK it
// set position AFTER undocking
var offset=this.getPanelOffset(p);
p.style.left=findPosX(p)-offset.x+'px'; p.style.top=findPosY(p)-offset.y+'px';
if (front) this.bringPanelToFront(p);
this.notify(p);
},
dockPanel: function(p) { // reset to docked pos/size
if (!hasClass(p,'undocked')) return; // already docked
this.restorePanel(p); // reset panel
// FOR FLOATING SLIDERS: trigger slider adjustment handler (if any)
if (hasClass(p,'floatingPanel') && window.adjustSliderPos)
window.adjustSliderPos(p.parentNode,p.button,p);
this.quiet++; if (this.manager) this.manager.trackMap(p); this.quiet--;
this.notify(p)
},
closePanel: function(p) { // dock panel, then close (for tiddlers and floating sliders)
var t=story.findContainingTiddler(p);
var isTiddler=t&&this.findPanel(t.getAttribute('tiddler'));
var isFloating=hasClass(p,'floatingPanel');
if (!isTiddler) // when closing TIDDLERS, leave them undocked (keeps size/pos)
this.dockPanel(p);
// FOR FLOATING SLIDERS: set focus and do a fake click on slider button
if (isFloating) { p.button.focus(); onClickNestedSlider({target:p.button}); }
// FOR TIDDLERS: call story.closeTiddler()
if (isTiddler) { story.closeTiddler(t.getAttribute('tiddler')); }
},
movePanel: function(p,x,y,show,centered) { if (!p) return;
this.quiet++;
this.undockPanel(p);
// adjust for child elements inside relative/floatingPanel containers
var offset=this.getPanelOffset(p);
p.style.left=x-offset.x+'px'; p.style.top=y-offset.y+'px';
if (show) { this.bringPanelToFront(p); this.scrollToPanel(p,centered); }
this.quiet--;
this.showPanelStatus(p,true);
if (this.manager) this.manager.trackMap(p);
},
foldPanel: function(p) { // toggle panel height
if (hasClass(p,'folded')) removeClass(p,'folded'); else addClass(p,'folded');
if (this.manager) this.manager.trackMap(p);
this.notify(p);
},
hoverPanel: function(p) { // toggle fixed position
if (hasClass(p,'hover')) {
removeClass(p,'hover');
var offset=this.getPanelOffset(p);
p.style.left=p.offsetLeft+findScrollX()-offset.x+'px';
p.style.top=p.offsetTop+findScrollY()-offset.y+'px';
} else {
var offset=this.getPanelOffset(p);
var ww=findWindowWidth(); var wh=findWindowHeight();
p.style.left=(p.offsetLeft-findScrollX()+offset.x)%ww+'px';
p.style.top =(p.offsetTop -findScrollY()+offset.y)%wh+'px';
addClass(p,'hover');
}
if (this.manager) this.manager.trackMap(p);
this.notify(p);
},
resetPanel: function(p) { // reset to session starting pos/size
if (this.manager) this.manager.resetPanel(p); else this.dockPanel(p);
},
//}}}
// // menu buttons
//{{{
processed: function(ev) { var ev=ev||window.event; // use to end event handling for menus and mouse actions
if (ev) { ev.cancelBubble=true; if (ev.stopPropagation) ev.stopPropagation(); } return false;
},
addPanelButtons: function(p,showfold,showhover,showclose,showdock,showmanager) {
if (p.menu) return; // only once per panel
function cmd(menu,label,tip,callback,show,arg) {
var fn=function(ev){return this.callback.apply(config.macros.moveablePanel,[this,ev,this.arg]);}
var b=createTiddlyButton(menu,label,tip,fn,'moveablePanelButton');
b.style.display=show?'inline':'none'; b.callback=callback; b.arg=arg;
return b;
}
var m=createTiddlyElement(p,'div',null,'moveablePanelMenu');
p.showfold=showfold;
p.foldbutton= cmd(m,this.foldLabel,this.foldTip,this.foldHandler,showfold);
p.unfoldbutton= cmd(m,this.unfoldLabel,this.unfoldTip,this.foldHandler,false);
p.showhover=showhover;
p.hoverbutton=cmd(m,this.hoverLabel,this.hoverTip,this.hoverHandler,showhover);
p.scrollbutton=cmd(m,this.scrollLabel,this.scrollTip,this.hoverHandler,false);
p.showdock=showdock;
p.dockbutton= cmd(m,this.dockLabel,this.dockTip,this.dockHandler,showdock);
p.showclose=showclose;
p.closebutton=cmd(m,this.closeLabel,this.closeTip,this.closeHandler,showclose);
p.showmanager=showmanager;
if (this.manager) p.managerbutton=cmd(m,this.manager.buttonLabel,this.manager.buttonTip,
this.manager.popup,showmanager,p.pid);
p.menu=m;
},
togglePanelButtons: function(p,show) { if (!p||!p.menu) return;
var undocked=hasClass(p,'undocked');
var floating=hasClass(p,'floatingPanel');
var hover=hasClass(p,'hover');
var folded=hasClass(p,'folded');
var t=story.findContainingTiddler(p);
var tiddler=t&&this.findPanel(t.getAttribute('tiddler'));
var show=show&&(undocked||floating);
p.menu.style.display=show?'inline':'none';
if (p.showfold) p.foldbutton.style.display =!folded?'inline':'none';
if (p.showfold) p.unfoldbutton.style.display= folded?'inline':'none';
if (p.showhover) p.hoverbutton.style.display =!hover?'inline':'none';
if (p.showhover) p.scrollbutton.style.display= hover?'inline':'none';
if (p.showdock) p.dockbutton.style.display =undocked?'inline':'none';
if (p.showclose) p.closebutton.style.display=floating||(tiddler&&undocked)?'inline':'none';
if (p.managerbutton) { // see [[PanelManagerPlugin]]
var show=p.showmanager||config.options.chkMoveablePanelShowManager;
p.managerbutton.style.display=show?'inline':'none';
}
},
foldHandler: function(place,ev){ var p=this.getPanel(place);
this.foldPanel(p); this.togglePanelButtons(p,true); return this.processed(ev); },
hoverHandler: function(place,ev){ var p=this.getPanel(place);
this.hoverPanel(p); this.togglePanelButtons(p,true); return this.processed(ev); },
dockHandler: function(place,ev){ var p=this.getPanel(place);
this.dockPanel(p); this.togglePanelButtons(p,true); return this.processed(ev); },
closeHandler: function(place,ev){ var p=this.getPanel(place);
this.closePanel(p); this.togglePanelButtons(p,true); return this.processed(ev); },
//}}}
// // mouse handlers
//{{{
addMouseHandlers: function(p) {
if (p.handlers) return true; // only add handlers ONCE
p.onmouseover=function(ev) { var ev=ev||window.event;
var r=config.macros.moveablePanel.mouseover(this,ev);
return r&&this.saved.mouseover?this.saved.mouseover.apply(this,arguments):true;
};
p.onmouseout=function(ev) { var ev=ev||window.event;
var r=config.macros.moveablePanel.mouseout(this,ev);
return r&&this.saved.mouseout?this.saved.mouseout.apply(this,arguments):true;
};
p.onmousemove=function(ev) { var ev=ev||window.event;
var r=config.macros.moveablePanel.mousemove(this,ev);
return r&&this.saved.mousemove?this.saved.mousemove.apply(this,arguments):true;
};
p.ondblclick=function(ev) { var ev=ev||window.event;
var r=config.macros.moveablePanel.dblclick(this,ev);
return r&&this.saved.dblclick?this.saved.dblclick.apply(this,arguments):r;
};
p.onmousedown=function(ev) { var ev=ev||window.event;
var r=config.macros.moveablePanel.mousedown(this,ev);
return r&&this.saved.mousedown?this.saved.mousedown.apply(this,arguments):r;
};
p.handlers=true;
},
isEdge: function(p,ev) { // near 'edge' of panel (or child element)?
var ev=ev||window.event; var target=resolveTarget(ev);
if (!p) return false;
// ignore form input fields
if (['input','select','option','textarea'].contains(target.nodeName.toLowerCase())) return false;
var left=findPosX(p); var top=findPosY(p);
var width=p.offsetWidth; var height=p.offsetHeight;
var x=findMouseX(ev); var y=findMouseY(ev);
if (hasClass(p,'hover')) { x-=findScrollX(); y-=findScrollY(); } // window-relative panel
if (x<left||y<top||x>=left+width||y>=top+height) { // outside of panel
if (p==target || p!=this.getPanel(target)) return false;
return this.isEdge(target,ev); // check target child element
}
var edgeW=this.getPanelEdgeWidth(p); var edgeH=this.getPanelEdgeHeight(p);
var isT=(y-top<edgeH); var isL=(x-left<edgeW);
var isB=(top+height-y<edgeH); var isR=(left+width-x<edgeW);
return isT||isL||isB||isR;
},
// temporary element during move/size keeps document from shrinking
addGhost: function(p) {
var g=document.getElementById('moveablePanelGhost');
if (!g) g=createTiddlyElement(document.body,'div','moveablePanelGhost','moveablePanelGhost');
var border=1; // note: must match CSS for 'moveablePanelGhost' WFFL-HACK
g.style.left=findPosX(p)+'px';
g.style.top=findPosY(p)+'px';
g.style.width=((p.offsetWidth-border*2)||0)+'px';
g.style.height=((p.offsetHeight-border*2)||0)+'px';
},
clearGhost: function() {
var e=document.getElementById('moveablePanelGhost');
if (e) e.parentNode.removeChild(e);
},
// MOUSEOVER=SHOW MENU
mouseover: function(place,ev) { var ev=ev||window.event;
var p=this.getPanel(place);
addClass(p,'selected'); // shows toolbar-classed items
this.togglePanelButtons(p,true);
return true;
},
// MOUSEOUT=HIDE MENU
mouseout: function(place,ev) { var ev=ev||window.event;
var p=this.getPanel(place);
removeClass(p,'selected'); // hides toolbar-classed items
this.togglePanelButtons(p,false);
return true;
},
// MOUSEMOVE=SHOW MENU AND SET CURSOR/TIP
mousemove: function(place,ev) { var ev=ev||window.event;
var p=this.getPanel(place);
p.style.cursor='auto'; p.title=p.saved?p.saved.title:'';
if (!this.isEdge(p,ev)) return true;
var fw=p.fixedwidth; if (fw==null) fw=undefined;
var fh=p.fixedheight; if (fh==null) fh=undefined;
p.title=this.moveTip.format([p.pid?p.pid+': ':'']);
if (fw===undefined&&fh===undefined) p.title+=' '+this.sizeTip;
else if (fw===undefined) p.title+=' '+this.sizeWidthTip;
else if (fh===undefined) p.title+=' '+this.sizeHeightTip;
if (hasClass(p,'undocked')) {
p.title+=', '+this.clickTip+', ';
p.title+=hasClass(p,'folded')?this.dblclickunfoldTip:this.dblclickdockTip;
}
p.style.cursor='move';
if (ev.shiftKey&&!(fw&&fh)) { // set resizing cursor (if not fixed width/height)
var left=findPosX(p); var top=findPosY(p);
var width=p.offsetWidth; var height=p.offsetHeight;
var x=findMouseX(ev); var y=findMouseY(ev);
if (hasClass(p,'hover')) { x-=findScrollX(); y-=findScrollY(); } // window-relative panel
var edgeW=this.getPanelEdgeWidth(p); var edgeH=this.getPanelEdgeHeight(p);
var isT=(y-top<edgeH); var isL=(x-left<edgeW);
var isB=(top+height-y<edgeH); var isR=(left+width-x<edgeW);
p.style.cursor=(fh===undefined?(isT?'n':(isB?'s':'')):'')
+(fw===undefined?(isL?'w':(isR?'e':'')):'')+'-resize';
}
return true;
},
// DOUBLE-CLICK=DOCK OR UNFOLD
dblclick: function(place,ev) { var ev=ev||window.event;
var p=this.getPanel(place);
if (!this.isEdge(p,ev)) return true;
// if folded... unfold, otherwise... undock
if (hasClass(p,'folded')) this.foldPanel(p); else this.dockPanel(p);
this.togglePanelButtons(p,false);
return this.processed(ev);
},
// MOUSEDOWN=START MOVE/SIZE, CLICK=BRING TO FRONT, SHIFT-CLICK=SEND TO BACK
mousedown: function(place,ev) { var ev=ev||window.event;
var p=this.getPanel(place);
// CLICK ALWAYS BRINGS TO FRONT
this.quiet++;
this.bringPanelToFront(p);
if (this.manager) this.manager.trackMap(p);
this.quiet--;
if (!this.isEdge(p,ev)) return true;
// start capturing mouse events and set mouse/key handlers
var target=p; // if 'capture' not supported, track in panel only
if (document.body.setCapture) // IE
{ document.body.setCapture(); var target=document.body; }
if (window.captureEvents) // moz
{ window.captureEvents(Event.MouseMove|Event.MouseUp,true); var target=window; }
if (target.onmousemove!=undefined) target.saved_mousemove=target.onmousemove;
target.onmousemove=this.dragmove;
if (target.onmouseup!=undefined) target.saved_mouseup=target.onmouseup;
target.onmouseup=this.dragstop;
if (target.onkeydown!=undefined) target.saved_keydown=target.onkeydown;
target.onkeydown=this.dragkey;
// calculate and save drag data in target element
var x=findMouseX(ev); var left=findPosX(p); var width =p.offsetWidth;
var y=findMouseY(ev); var top =findPosY(p); var height=p.offsetHeight;
var sizing=ev.shiftKey;
var edgeW=this.getPanelEdgeWidth(p); var edgeH=this.getPanelEdgeHeight(p);
var isT=(y-top<edgeH); var isL=(x-left<edgeW);
var isB=(top+height-y<edgeH); var isR=(left+width-x<edgeW);
var d=new Object();
d.panel=p; d.left=left; d.top=top;
d.width=this.getPanelWidth(p); d.height=this.getPanelHeight(p);
d.sizing=sizing; d.edgeW=edgeW; d.edgeH=edgeH;
d.isT=isT; d.isL=isL; d.isB=isB; d.isR=isR; d.offset=this.getPanelOffset(p);
d.saved={ x:p.style.left, y:p.style.top, w:p.style.width, h:p.style.height,
z:p.style.zIndex, pos:p.style.position, classname:p.className };
target.data=d;
this.addGhost(p); // keep document from shrinking during move/size
return this.processed(ev);
},
// MOUSEMOVE (during drag)=move/size panel
dragmove: function(ev){ var ev=ev||window.event; var cmm=config.macros.moveablePanel;
var d=this.data; var p=d.panel;
if (!p) { this.onmousemove=this.saved_mousemove?this.saved_mousemove:null; return; }
cmm.quiet++; // save all notifications until the end...
// ensure panel is undocked and scrolled into view, THEN get starting mouse and scroll positions
if (!hasClass(p,'undocked'))
{ cmm.undockPanel(p,true); if (this.manager) this.manager.trackMap(p); }
if (d.x===undefined) // first move event only
{ cmm.scrollToPanel(p); d.x=findMouseX(ev); d.y=findMouseY(ev); }
// get current mouse pos
var newX=findMouseX(ev); var newY=findMouseY(ev);
// calculate new TLWH (start with current panel pos/size)
var startX=d.x; var startY=d.y; var offsetX=d.offset.x; var offsetY=d.offset.y;
var L=d.left; var T=d.top; var W=d.width; var H=d.height;
var newL=L; var newT=T; var newW=p.fixedwidth||W; var newH=p.fixedheight||H;
if (d.sizing) { // resize panel
var minW=d.edgeW*2; var minH=d.edgeH*2; // stay bigger than edge areas
if (hasClass(p,'folded')) this.fold(p.foldButton,ev); // un-fold first!
if (d.isT) newH=H-newY+startY+1;
if (d.isB) newH=H+newY-startY+1;
if (d.isL) newW=W-newX+startX+1;
if (d.isR) newW=W+newX-startX+1;
if (d.isT) newT=T-offsetY+newY-startY+1; else newT=T-offsetY;
if (d.isL) newL=L-offsetX+newX-startX+1; else newL=L-offsetX;
if ((d.isL||d.isR)&&!p.fixedwidth) newW=(newW>minW?newW:minW);
if ((d.isT||d.isB)&&!p.fixedheight) newH=(newH>minH?newH:minH);
} else { // move panel
newL=L-offsetX+newX-startX+1;
newT=T-offsetY+newY-startY+1;
}
if (hasClass(p,'hover')) { // hover=stay on first screen
var ww=findWindowWidth(); var wh=findWindowHeight();
newL+=offsetX; newT+=offsetY; // hover=no relative offset (window-relative)
// WFFL lower right is off... a bit too far (perhaps scrollwidth?)
if (newL+newW>ww) newL=ww-newW; if (newT+newH>wh) newT=wh-newH; // limit lower right
if (newL<0) newL=0; if (newT<0) newT=0; // limit upper left
} else { // normal floating panel=limit upper left (stay on page)
if (newL+offsetX<0) newL=0-offsetX; if (newT+offsetY<0) newT=0-offsetY;
}
// move the panel and scroll into view as needed
p.style.left=newL.toString()+'px';
p.style.top=newT.toString()+'px';
if (d.sizing) p.style.width=newW.toString()+'px';
if (d.sizing) p.style.height=newH.toString()+'px';
cmm.scrollToPanel(p);
// report new position and notify panel manager... done!
cmm.quiet--; cmm.showPanelStatus(p,true); cmm.notify(p);
return cmm.processed(ev);
},
dragkey: function(ev){ var ev=ev||window.event;
var d=this.data; var p=d.panel;
if (ev.keyCode==27) { // ESC=CANCEL... restore panel to previous pos/size
p.style.left =d.saved.x; p.style.top =d.saved.y;
p.style.width=d.saved.w; p.style.height=d.saved.h;
p.style.zIndex=d.saved.z;
p.style.position=d.saved.pos;
p.className=d.saved.classname;
return this.onmouseup(ev);
}
if (this.saved_keydown) return this.saved_keydown(ev);
},
// MOUSEUP: END MOVE/SIZE, SHIFT-CLICK=SEND TO BACK
dragstop: function(ev){ var ev=ev||window.event; var cmm=config.macros.moveablePanel;
var newX=findMouseX(ev); var newY=findMouseY(ev);
if (this.releaseCapture) this.releaseCapture(); // IE
if (this.releaseEvents) this.releaseEvents(Event.MouseMove|Event.MouseUp); // moz
this.onmousemove=this.saved_mousemove?this.saved_mousemove:null;
this.onmouseup=this.saved_mouseup?this.saved_mouseup:null;
this.onkeydown=this.saved_keydown?this.saved_keydown:null;
var d=this.data; var p=d.panel;
if (ev.shiftKey && d.x==newX && d.y==newY && cmm.isEdge(p,ev))
cmm.sendPanelToBack(p); // SHIFT-CLICK *EDGE*
cmm.togglePanelButtons(p,true);
cmm.quiet++; if (cmm.manager) cmm.manager.trackMap(p); cmm.quiet--;
cmm.clearGhost(); // allow document to adjust extents (if needed)
cmm.showPanelStatus(p,false); cmm.timedMessage(cmm.formatPanelStatus(p),cmm.msgDuration);
return cmm.processed(ev);
},
//}}}
// // CSS definitions
//{{{
css: '/*{{{*/\n'
+'.moveablePanelMenu\n'
+'\t{ display:none; position:absolute; right:.5em; top:-1em; }\n'
+'.undocked .selected.moveablePanelMenu\n'
+'\t{ display:inline; }\n'
+'.floatingPanel .selected .moveablePanelMenu\n'
+'\t{ display:inline; }\n'
+'.hover\n'
+'\t{ position:fixed !important; }\n'
+'.folded\n'
+'\t{ height:1.5em !important; overflow:hidden !important; }\n'
+'.tiddler .folded\n'
+'\t{ height:2em !important; }\n'
+'.folded .moveablePanelMenu\n'
+'\t{ top:.5em; } /* buttons fit in folded panel */\n'
+'.tiddler .moveablePanelMenu\n'
+'\t{ top:.2em; } /* buttons fit in tiddler title */\n'
+'.undocked .toolbar\n'
+'\t{ padding-right:7.5em; } /* make room for buttons next to toolbar */\n'
+'.floatingPanel .moveablePanelMenu\n'
+'\t{ right:1em;top:1em; } /* buttons fit in floating panel */\n'
+'.moveablePanelButton\n {'
+'\tbackground:#ccc !important; color:#000 !important;\n'
+'\tborder:1px solid #666; padding:0 .25em; margin:0px 1px;\n'
+'}\n'
+'.moveablePanelButton:hover\n'
+'\t{ background:#fff !important; color:#000 !important; }\n'
+'.popup\n'
+'\t{ z-index:9999999 !important; } /* popups MUST always be on top! */\n'
+'.moveablePanelGhost\n'
+'\t{ position:absolute; border:1px dotted #999; }\n'
+'/*}}}*/'
});
//}}}
// // load time initialization
//{{{
// defaults for options
if (config.options.txtMoveablePanelMapName===undefined)
config.options.txtMoveablePanelMapName='DefaultMap';
if (config.options.chkMoveablePanelShowStatus===undefined)
config.options.chkMoveablePanelShowStatus=true;
if (config.options.chkMoveablePanelShowManager===undefined)
config.options.chkMoveablePanelShowManager=true;
// set up shadow stylesheet, then load styles (might be customized)
config.shadowTiddlers.MoveablePanelStyles=config.macros.moveablePanel.css;
var css=store.getRecursiveTiddlerText('MoveablePanelStyles',config.macros.moveablePanel.css,10);
setStylesheet(css,'moveablePanelStyles');
//}}}
// // hijacks
//{{{
// adjust popup placement to account for the current horizontal scrollbar
// offset (if any), so that popups will appear in the correct location, even
// when their 'root' element is scrolled far from the page origin.
var fn=Popup.place; fn=fn.toString(); if (fn.indexOf('findScrollX')==-1) { // only once
fn=fn.replace(/winWidth\s*-\s*scrollWidth\s*-\s*1/,
'findScrollX() + winWidth - scrollWidth - 1');
fn=fn.replace(/winWidth\s*-\s*popupWidth\s*-\s*scrollWidth\s*-\s*1/,
'findScrollX() + winWidth - popupWidth - scrollWidth - 1');
eval('Popup.place='+fn);
}
//}}}
//{{{
// window.scrollTo() is used throughout the core (and plugins) to scroll to a *vertical*
// position as computed by ensureVisible(), in order to bring a tiddler into view.
// Unfortunately, the *horizontal* scroll position is almost always hard-coded to 0
// (i.e. a return to the left edge of the page). Normally, this is not a problem,
// since a page is rarely scrolled horizontally. However, when there are moveable
// panels, they can appear far from the left edge, so always scrolling to the left
// edge is very disruptive. In addition, unwanted scrolling can occur as a side
// effect when displaying or refreshing a tiddler (e.g., switching between
// view/edit templates). These hijacks adds control flags ('noScrollX' and 'noscrollY')
// that can be used to temporarily disable scrolling within the document.
if (window.scrollTo_moveablePanel==undefined) { // only once
window.scrollTo_moveablePanel=window.scrollTo;
window.scrollTo=function(x,y) {
var cmm=config.macros.moveablePanel;
if (cmm.noScrollX&&cmm.noScrollY) return;
x=cmm.noScrollX?findScrollX():x;
y=cmm.noScrollY?findScrollY():y;
window.scrollTo_moveablePanel(x,y);
}
}
// ensureVisible() is used to calculate the y-offset of a tiddler, just before scrolling
// This tweak sets up an ASYNC timer to invoke the 'bring to front/scroll into view'
// function for 'tiddler' and 'popup' classes. This allows the function
// to be triggered *after* core scrolling occurs, so the fixups are not
// stomped on by the core's normal scroll handling
if (window.ensureVisible_moveablePanel==undefined) { // only once
window.ensureVisible_moveablePanel=window.ensureVisible;
window.ensureVisible=function(e) {
var ny=ensureVisible_moveablePanel.apply(this,arguments); // get core value
// fixup height to account for horizontal scrollbar (if present)
var atBottom=findPosY(e)+e.offsetHeight>=findScrollY()+findWindowHeight();
var hasHScroll=document.documentElement.scrollWidth>findWindowWidth();
var hScrollSize=findWindowWidth()-document.body.offsetWidth;
if (atBottom && hasHScroll) ny+=hScrollSize;
// defer scrolling for tiddlers and popups (except during startup)
if (!startingUp && (hasClass(e,'tiddler')||hasClass(e,'popup'))) {
var cmm=config.macros.moveablePanel;
cmm.noScrollX++; if (hasClass(e,'tiddler')) cmm.noScrollY++;
var delay=config.options.chkAnimate?config.animDuration+50:50;
cmm.ensurePanelVisible(e,delay); // ASYNC SCROLL
}
return ny;
}
}
// story.refreshTiddler()
if (Story.prototype.refreshTiddler_moveablePanel==undefined) { // only once
Story.prototype.refreshTiddler_moveablePanel=Story.prototype.refreshTiddler;
Story.prototype.refreshTiddler=function() {
var cmm=config.macros.moveablePanel;
cmm.noScrollX++; cmm.noScrollY++; // DON'T SCROLL AT ALL
var r=this.refreshTiddler_moveablePanel.apply(this,arguments);
cmm.noScrollX--; cmm.noScrollY--;
return r;
}
}
// story.displayTiddler()
if (Story.prototype.displayTiddler_moveablePanel==undefined) { // only once
Story.prototype.displayTiddler_moveablePanel=Story.prototype.displayTiddler;
Story.prototype.displayTiddler=function(srcElement,tiddler) {
var cmm=config.macros.moveablePanel;
//WFFL cmm.noScrollX++; cmm.noScrollY++;
var r=this.displayTiddler_moveablePanel.apply(this,arguments);
var title=(tiddler instanceof Tiddler)?tiddler.title:tiddler;
var panel=cmm.findPanel(title); // if moveable... unfold panel (but not during startup)
if (panel&&hasClass(panel,'folded')&&!startingUp) cmm.foldPanel(panel);
var delay=config.options.chkAnimate?config.animDuration+50:50;
cmm.ensurePanelVisible(this.getTiddler(title),delay); // ASYNC SCROLL
return r;
}
}
//}}}
//{{{
// Zoomer() displays an animated bounding box when showing a tiddler. But this box 'zooms' to the tiddler's 'anchor point', not the current panel position, which can cause a 'scroll blink'. This hijack redirects the zoomer's target directly to the undocked panel (if any)
if (window.Zoomer_moveablePanel==undefined) { // only once
window.Zoomer_moveablePanel=window.Zoomer;
window.Zoomer=function(text,startElement,targetElement,unused) {
if (hasClass(targetElement,'tiddler')) {
var tid=targetElement.getAttribute('tiddler');
arguments[2]=config.macros.moveablePanel.findPanel(tid)||targetElement;
}
return window.Zoomer_moveablePanel.apply(this,arguments);
}
}
//}}}
/***
|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|
!!!!!Documentation
>see [[NestedSlidersPluginInfo]]
!!!!!Configuration
<<<
<<option chkFloatingSlidersAnimate>> allow floating sliders to animate when opening/closing
>Note: This setting can cause 'clipping' problems in some versions of InternetExplorer.
>In addition, for floating slider animation to occur you must also allow animation in general (see [[AdvancedOptions]]).
<<<
!!!!!Revisions
<<<
2008.11.15 - 2.4.9 in adjustNestedSlider(), don't make adjustments if panel is marked as 'undocked' (CSS class). In onClickNestedSlider(), SHIFT-CLICK docks panel (see [[MoveablePanelPlugin]])
|please see [[NestedSlidersPluginInfo]] for additional revision details|
2005.11.03 - 1.0.0 initial public release. Thanks to RodneyGomes, GeoffSlocock, and PaulPetterson for suggestions and experiments.
<<<
!!!!!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);
}
};
}
//}}}
/***
!NestedSlidersPlugin
***/
/*{{{*/
.floatingPanel
{ z-index:700; padding:1em; margin:0em; border:1px solid; -moz-border-radius:1em;-webkit-border-radius:1em; font-size:8pt; text-align:left; }
.floatingPanel hr
{ margin:2px 0 1px 0; padding:0; }
#sidebarOptions .sliderPanel
{ margin:0; padding:0; font-size:1em; background:transparent; }
#sidebarOptions .sliderPanel a
{ font-weight:normal; }
#sidebarOptions .sliderPanel blockquote
{ margin:0;padding:0;margin-left:1em; border-left:1px dotted; padding-left:1em }
.selected .floatingPanel .button,
.selected .floatingPanel a:link,
.selected .floatingPanel a:hover,
.selected .floatingPanel a:visited,
.floatingPanel .button,
.floatingPanel a:link,
.floatingPanel a:hover,
.floatingPanel a:visited
{ color:[[ColorPalette::PrimaryDark]] !important; }
/*}}}*/
/***
|''Name:''|PasswordOptionPlugin|
|''Description:''|Extends TiddlyWiki options with non encrypted password option.|
|''Version:''|1.0.2|
|''Date:''|Apr 19, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#PasswordOptionPlugin|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0 (Beta 5)|
***/
//{{{
version.extensions.PasswordOptionPlugin = {
major: 1, minor: 0, revision: 2,
date: new Date("Apr 19, 2007"),
source: 'http://tiddlywiki.bidix.info/#PasswordOptionPlugin',
author: 'BidiX (BidiX (at) bidix (dot) info',
license: '[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D]]',
coreVersion: '2.2.0 (Beta 5)'
};
config.macros.option.passwordCheckboxLabel = "Save this password on this computer";
config.macros.option.passwordInputType = "password"; // password | text
setStylesheet(".pasOptionInput {width: 11em;}\n","passwordInputTypeStyle");
merge(config.macros.option.types, {
'pas': {
elementType: "input",
valueField: "value",
eventName: "onkeyup",
className: "pasOptionInput",
typeValue: config.macros.option.passwordInputType,
create: function(place,type,opt,className,desc) {
// password field
config.macros.option.genericCreate(place,'pas',opt,className,desc);
// checkbox linked with this password "save this password on this computer"
config.macros.option.genericCreate(place,'chk','chk'+opt,className,desc);
// text savePasswordCheckboxLabel
place.appendChild(document.createTextNode(config.macros.option.passwordCheckboxLabel));
},
onChange: config.macros.option.genericOnChange
}
});
merge(config.optionHandlers['chk'], {
get: function(name) {
// is there an option linked with this chk ?
var opt = name.substr(3);
if (config.options[opt])
saveOptionCookie(opt);
return config.options[name] ? "true" : "false";
}
});
merge(config.optionHandlers, {
'pas': {
get: function(name) {
if (config.options["chk"+name]) {
return encodeCookie(config.options[name].toString());
} else {
return "";
}
},
set: function(name,value) {config.options[name] = decodeCookie(value);}
}
});
// need to reload options to load passwordOptions
loadOptionsCookie();
/*
if (!config.options['pasPassword'])
config.options['pasPassword'] = '';
merge(config.optionsDesc,{
pasPassword: "Test password"
});
*/
//}}}
/%
!info
|Name|RefreshTiddler|
|Source|http://www.TiddlyTools.com/#RefreshTiddler|
|Version|2.0.0|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|transclusion|
|Description|create a link to force an immediate refresh of the current tiddler|
Usage
<<<
{{{
<<tiddler RefreshTiddler>>
<<tiddler RefreshTiddler with: label tip>>
}}}
<<<
Example
<<<
{{{<<tiddler RefreshTiddler with: "click me">>}}}
<<tiddler RefreshTiddler##show with: "click me">>
content displayed at <<today 0hh:0mm:0ss>>
<<<
!end
!show
<html><nowiki><a href="javascript:;" title="$2"
onclick="
var here=story.findContainingTiddler(this);
if (here) story.refreshTiddler(here.getAttribute('tiddler'),null,true);
return false;
">$1</a></html>
!end
%/<<tiddler {{var src='RefreshTiddler'; src+(tiddler&&tiddler.title==src?'##info':'##show')}}
with: {{'$1'!='$'+'1'?'$1':'refresh'}}
{{'$2'!='$'+'2'?'$2':'redisplay current tiddler content'}}
>>
<<tiddler RefreshTiddler>>
/***
|Name|SectionLinksPlugin|
|Source|http://www.TiddlyTools.com/#SectionLinksPlugin|
|Documentation|http://www.TiddlyTools.com/#SectionLinksPlugin|
|Version|1.4.2|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|allow tiddler sections in TiddlyLinks to be used as anchor points|
This plugin enhances tiddler links so that they can include section references that ''auto-scroll to the indicated section heading'' within a tiddler (i.e., similar to the 'anchor' behavior provided in HTML by {{{<a name="foo">}}} and {{{<a href="#foo">...</a>}}}). The {{{<<tiddler>>}}} macro syntax has also be extended to allow section references without a tiddler name, so that transclusion of //hidden sections from the same tiddler// can be easily accomplished. The plugin also adds a new macro, <<sectionTOC>> which can auto-generate and embed a 'Table of Contents' outline view into a tiddler to enable quick navigation to sections within that tiddler.
!!!Usage
<<<
!!!!~TiddlyLink syntax
You can link to a section of a tiddler by adding the "##sectionname" syntax to the tiddlername:
{{{
[[SomeTiddler##SomeSection]]
}}}
When clicked, the tiddler is displayed and the specified section heading is automatically scrolled into view. If the tiddler title is omitted or the 'here' keyword is used, e.g.,
{{{
[[##SomeSection]] or [[here##SomeSection]]>>
}}}
then the current containing tiddler is implied by default.
!!!!HTML anchor syntax
You can use HTML syntax to create a scrollable 'anchor' location within a tiddler without use of the standard TW section heading syntax:
{{{
<html><a name="sectionname" /></html>
}}}
You can then link to that section using the enhanced TiddlyLink syntax as above.
!!!!{{{<<tiddler>>}}} macro
The {{{<<tiddler>>}}} syntax has been extended so that when the tiddler title is omitted or the 'here' keyword is used, e.g.,
{{{
<<tiddler ##SomeSection>> or <<tiddler here##SomeSection>>
}}}
then the current containing tiddler is implied by default.
!!!!"""<<sectionTOC>>""" macro
This macro generates a 'Table of Contents' outline-style bullet list with links to all sections within the current tiddler. Simply place the following macro at the //end of the tiddler content// (i.e., following all section headings). Important note: //''The {{{<<sectionTOC>>}}} macro must occur at the end of the tiddler in order to locate the rendered section headings that precede it.''//
{{{
<<sectionTOC>> or <<sectionTOC className>>
}}}
To position the macro's //output// within the tiddler, you must create a special 'target element' that uses a specific classname (default='sectionTOC'), like this:
{{{
{{sectionTOC{}}}
}}}
When the {{{<<sectionTOC>>}}} macro is rendered, it will find the matching 'sectionTOC'-classed element and writes it's output there. You can also add the macro and/or target elements directly to the [[ViewTemplate]] definition, so that every tiddler can automatically display the table of contents:
{{{
<span class='sectionTOC'></span> <!-- target element -->
...
<span macro='sectionTOC'></span> <!-- must be at end of tiddler -->
}}}
<<<
!!!Configuration
<<<
You can change the {{{<<SectionTOC>>}}} output link format by adding the following statement to a tiddler tagged with <<tag systemConfig>>
{{{
config.macros.sectionTOC.linkFormat='[[%0|##%0]]';
}}}
The default value (shown above) produces a link to each section within the tiddler, using "%0" to insert the section name into the link. You can add extra formatting to generate additional output to suit your purposes. For example, if you have EditSectionPlugin installed, you could include a link that invokes that plugin's popup editor directly from each item in the TOC display, like this:
{{{
config.macros.sectionTOC.linkFormat='[[%0|##%0]] <<editSection [[##%0]] [[(edit)]]>>';
}}}
<<<
!!!Examples
<<<
links to sections defined by ''TW heading syntax'' (e.g, {{{!!!sectionname}}}):{{indent{
[[SectionLinksPlugin##onClickTiddlerLink]]
[[##onClickTiddlerLink]] //(current tiddler implied)//}}}
links to anchors defined by ''HTML syntax'' (e.g., {{{<html><a href="anchorname"></html>}}}):{{indent{
[[SectionLinksPlugin##sampleanchorlink]]
[[##sampleanchorlink]] //(current tiddler implied)//}}}
<<<
!!!Revisions
<<<
2011.12.21 1.4.2 refactor sectionTOCformat to permit customization
2011.02.08 1.4.1 in isExternalLink() hijack, strip section references before testing for external link
2010.08.09 1.4.0 in scrollToSection(), added support for using HTML <a name="..."> anchor elements
2009.08.21 1.3.4 added handling to ignore leading/trailing whitespace in section references
2009.08.21 1.3.3 in createTiddlyLink(), add tiddlyLinkNonExistingSection class if matching section is not found
2009.08.14 1.3.2 in createTiddlyLink(), don't override core value for ~TiddlyLink attribute
2009.08.02 1.3.1 in sectionTOC.handler(), trim leading/trailing whitespace from generated section links
2009.08.01 1.3.0 in scrollToSection(), apply 3-tier section matching (exact, startsWith, contains)
2009.07.06 1.2.2 fixed displayTiddler() hijack
2009.07.03 1.2.1 in {{{<<sectionTOC>>}}}, suppress output if target is not found
2009.06.02 1.2.0 added support for 'here' keyword in {{{[[here##section]]}}} links and {{{<<tiddler here##section>>}}} macro
2009.04.09 1.1.1 in sectionTOC macro, make target visible when TOC is rendered.
2009.01.18 1.1.0 added {{{<<sectionTOC>>}}} macro to generate numbered-bullet links to sections of current tiddler
2009.01.06 1.0.0 converted to stand-alone plugin
2008.10.14 0.0.0 initial release (as [[CoreTweaks]] #784 - http://trac.tiddlywiki.org/ticket/784)
<<<
!!!Code
***/
//{{{
version.extensions.SectionLinksPlugin= {major: 1, minor: 4, revision: 2, date: new Date(2011,12,21)};
Story.prototype.scrollToSection = function(title,section) {
if (!title||!section) return; var t=this.getTiddler(title); if (!t) return null;
var elems=t.getElementsByTagName('*');
var heads=[]; var anchors=[];
for (var i=0; i<elems.length; i++)
if (['H1','H2','H3','H4','H5'].contains(elems[i].nodeName)) heads.push(elems[i]);
for (var i=0; i<elems.length; i++)
if (elems[i].nodeName=='A' && (elems[i].getAttribute('name')||'').length) anchors.push(elems[i]);
var found=null;
for (var i=0; i<heads.length; i++)
if (getPlainText(heads[i]).trim()==section) { found=heads[i]; break; }
if (!found) for (var i=0; i<heads.length; i++)
if (getPlainText(heads[i]).trim().startsWith(section)) { found=heads[i]; break; }
if (!found) for (var i=0; i<heads.length; i++)
if (getPlainText(heads[i]).trim().indexOf(section)!=-1) { found=heads[i]; break; }
if (!found) for (var i=0; i<anchors.length; i++)
if (anchors[i].getAttribute('name')==section) { found=anchors[i]; break; }
if (!found) for (var i=0; i<anchors.length; i++)
if (anchors[i].getAttribute('name').startsWith(section)) { found=anchors[i]; break; }
if (!found) for (var i=0; i<anchors.length; i++)
if (anchors[i].getAttribute('name').indexOf(section)!=-1) { found=anchors[i]; break; }
if (found) {
// if section heading is collapsed, click to expand it - see [[FoldHeadingsPlugin]]
if (hasClass(found,'foldable') && found.nextSibling.style.display=='none') found.onclick();
// scroll *after* tiddler animation
var delay=config.options.chkAnimate?config.animDuration+100:0;
setTimeout('window.scrollTo('+findPosX(found)+','+findPosY(found)+')',delay);
return found;
}
}
//}}}
/***
!!!!core hijacks
***/
/***
!!!!!createTiddlyLink
***/
//{{{
// [[tiddlername##section]] and [[##section]]
if (!window.createTiddlyLink_section)
window.createTiddlyLink_section=window.createTiddlyLink;
window.createTiddlyLink=function(place,title) {
var t=story.findContainingTiddler(place); var tid=t?t.getAttribute('tiddler'):'';
var parts=title.split(config.textPrimitives.sectionSeparator);
var title=parts[0]; var section=parts[1]; if (section) section=section.trim();
if (!title.length || title.toLowerCase()=='here') title=tid; // default=current tiddler
arguments[1]=title;
var btn=createTiddlyLink_section.apply(this,arguments);
if (section) {
btn.setAttribute('section',section);
if (store.getTiddlerText(title+config.textPrimitives.sectionSeparator+section)===null)
addClass(btn,'tiddlyLinkNonExistingSection');
}
return btn;
}
//}}}
/***
!!!!!onClickTiddlerLink
***/
//{{{
if (!window.onClickTiddlerLink_section)
window.onClickTiddlerLink_section=window.onClickTiddlerLink;
window.onClickTiddlerLink=function(ev) {
var e=ev||window.event; var target=resolveTarget(e); var title=null;
while (target!=null && title==null) {
title=target.getAttribute('tiddlyLink');
section=target.getAttribute('section');
target=target.parentNode;
}
var t=story.findContainingTiddler(target); var tid=t?t.getAttribute('tiddler'):'';
if (title!=tid||!section) // avoid excess scrolling for intra-tiddler links
onClickTiddlerLink_section.apply(this,arguments);
story.scrollToSection(title,section);
return false;
}
//}}}
/***
!!!!! displayTiddler
***/
//{{{
if (!Story.prototype.displayTiddler_section)
Story.prototype.displayTiddler_section=Story.prototype.displayTiddler;
Story.prototype.displayTiddler = function(srcElement,tiddler)
{
var title=(tiddler instanceof Tiddler)?tiddler.title:tiddler;
var parts=title.split(config.textPrimitives.sectionSeparator);
var title=parts[0]; var section=parts[1]; if (section) section=section.trim();
if (!title.length || title.toLowerCase()=='here') {
var t=story.findContainingTiddler(place);
title=t?t.getAttribute('tiddler'):'';
}
arguments[1]=title; // default=current tiddler
this.displayTiddler_section.apply(this,arguments);
story.scrollToSection(title,section);
}
//}}}
/***
<html><a name="sampleanchorlink" /></html>This is a sample ''anchor link'': {{{<html><a name="sampleanchorlink" /></html>}}}
!!!!!isExternalLink
***/
//{{{
if (!config.formatterHelpers.isExternalLink_section)
config.formatterHelpers.isExternalLink_section=config.formatterHelpers.isExternalLink;
config.formatterHelpers.isExternalLink=function(link) { // remove section references before testing
var l=link.split(config.textPrimitives.sectionSeparator)[0];
return config.formatterHelpers.isExternalLink_section(l);
}
//}}}
/***
!!!!!tiddler.handler
***/
//{{{
if (!config.macros.tiddler.handler_section)
config.macros.tiddler.handler_section=config.macros.tiddler.handler;
config.macros.tiddler.handler=function(place,macroName,params,wikifier,paramString,tiddler)
{
if (!params[0]) return;
var sep=config.textPrimitives.sectionSeparator;
var parts=params[0].split(sep); var tid=parts[0]; var sec=parts[1]; if (sec) sec=sec.trim();
if ((tid.toLowerCase()=='here'||!tid.length) && sec) { // fixup for 'here##section' and '##section'
var here=story.findContainingTiddler(place)
var tid=here?here.getAttribute('tiddler'):tiddler?tiddler.title:'';
arguments[2][0]=tid+sep+sec;
arguments[4]=paramString.replace(new RegExp('(here)?'+sep+sec),tid+sep+sec);
}
config.macros.tiddler.handler_section.apply(this,arguments);
}
//}}}
/***
!!!!sectionTOC macro
***/
//{{{
config.macros.sectionTOC = {
targetClass: 'sectionTOC',
linkFormat: '[[%0|##%0]]',
handler: function(place,macroName,params,wikifier,paramString,tiddler) {
var out=[];
var targetClass=params[0]||this.targetClass;
var t=story.findContainingTiddler(place); if (!t) return;
var elems=t.getElementsByTagName('*');
var level=5; // topmost heading level
for (var i=0; i<elems.length; i++) {
var txt=getPlainText(elems[i]).trim();
var link=this.linkFormat.format([txt]);
switch(elems[i].nodeName) {
case 'H1': out.push('#'+link); level=1; break;
case 'H2': out.push('##'+link); level=level<2?level:2; break;
case 'H3': out.push('###'+link); level=level<3?level:3; break;
case 'H4': out.push('####'+link); level=level<4?level:4; break;
case 'H5': out.push('#####'+link); level=level<5?level:5; break;
default: if (hasClass(elems[i],targetClass)) var target=elems[i];
}
}
// trim excess bullet levels
if (level>1) for (var i=0; i<out.length; i++) out[i]=out[i].substr(level-1);
// show numbered list
if (out.length && target) {
if (target.style.display=='none') target.style.display='block';
wikify(out.join('\n'),target);
}
}
}
//}}}
/***
!!!Invoke macro
{{{
<<sectionTOC>>
}}}
***/
// //<<sectionTOC>>
@@position:relative;+++^18em^*[toc]{{sectionTOC{<<moveablePanel name:{{tiddler?tiddler.title:""}} docked width:18em height:auto fold hover noclose>>
<<tiddler [[SliderToc##EditIndex]] with:{{tiddler.title}}>>}}}
<html><div style="float:right;cursor:pointer;font-size:11pt;"><a href="javascript:window.scrollTo(0,0)" title="jump to the top of the page"> ⇑</a></div><br></html>===@@/%
!EditIndex
<<editSection [[$1##]] [[Index for $1]]>>
! %/
[[NestedSlidersPluginStyleSheet]]
|~ViewToolbar|closeTiddler closeOthers +editTiddler SliderToc > fields syncing permalink references jump < RefreshTiddlerToolbar|
|~EditToolbar|+saveTiddler -cancelTiddler deleteTiddler|
/***
Description: Contains the stuff you need to use Tiddlyspot
Note, you also need UploadPlugin, PasswordOptionPlugin and LoadRemoteFileThroughProxy
from http://tiddlywiki.bidix.info for a complete working Tiddlyspot site.
***/
//{{{
// edit this if you are migrating sites or retrofitting an existing TW
config.tiddlyspotSiteId = 'edittoc';
// make it so you can by default see edit controls via http
config.options.chkHttpReadOnly = false;
window.readOnly = false; // make sure of it (for tw 2.2)
window.showBackstage = true; // show backstage too
// disable autosave in d3
if (window.location.protocol != "file:")
config.options.chkGTDLazyAutoSave = false;
// tweak shadow tiddlers to add upload button, password entry box etc
with (config.shadowTiddlers) {
SiteUrl = 'http://'+config.tiddlyspotSiteId+'.tiddlyspot.com';
SideBarOptions = SideBarOptions.replace(/(<<saveChanges>>)/,"$1<<tiddler TspotSidebar>>");
OptionsPanel = OptionsPanel.replace(/^/,"<<tiddler TspotOptions>>");
DefaultTiddlers = DefaultTiddlers.replace(/^/,"[[WelcomeToTiddlyspot]] ");
MainMenu = MainMenu.replace(/^/,"[[WelcomeToTiddlyspot]] ");
}
// create some shadow tiddler content
merge(config.shadowTiddlers,{
'TspotOptions':[
"tiddlyspot password:",
"<<option pasUploadPassword>>",
""
].join("\n"),
'TspotControls':[
"| tiddlyspot password:|<<option pasUploadPassword>>|",
"| site management:|<<upload http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/store.cgi index.html . . " + config.tiddlyspotSiteId + ">>//(requires tiddlyspot password)//<br>[[control panel|http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/controlpanel]], [[download (go offline)|http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/download]]|",
"| links:|[[tiddlyspot.com|http://tiddlyspot.com/]], [[FAQs|http://faq.tiddlyspot.com/]], [[blog|http://tiddlyspot.blogspot.com/]], email [[support|mailto:support@tiddlyspot.com]] & [[feedback|mailto:feedback@tiddlyspot.com]], [[donate|http://tiddlyspot.com/?page=donate]]|"
].join("\n"),
'WelcomeToTiddlyspot':[
"This document is a ~TiddlyWiki from tiddlyspot.com. A ~TiddlyWiki is an electronic notebook that is great for managing todo lists, personal information, and all sorts of things.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //What now?// @@ Before you can save any changes, you need to enter your password in the form below. Then configure privacy and other site settings at your [[control panel|http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/controlpanel]] (your control panel username is //" + config.tiddlyspotSiteId + "//).",
"<<tiddler TspotControls>>",
"See also GettingStarted.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Working online// @@ You can edit this ~TiddlyWiki right now, and save your changes using the \"save to web\" button in the column on the right.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Working offline// @@ A fully functioning copy of this ~TiddlyWiki can be saved onto your hard drive or USB stick. You can make changes and save them locally without being connected to the Internet. When you're ready to sync up again, just click \"upload\" and your ~TiddlyWiki will be saved back to tiddlyspot.com.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Help!// @@ Find out more about ~TiddlyWiki at [[TiddlyWiki.com|http://tiddlywiki.com]]. Also visit [[TiddlyWiki.org|http://tiddlywiki.org]] for documentation on learning and using ~TiddlyWiki. New users are especially welcome on the [[TiddlyWiki mailing list|http://groups.google.com/group/TiddlyWiki]], which is an excellent place to ask questions and get help. If you have a tiddlyspot related problem email [[tiddlyspot support|mailto:support@tiddlyspot.com]].",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Enjoy :)// @@ We hope you like using your tiddlyspot.com site. Please email [[feedback@tiddlyspot.com|mailto:feedback@tiddlyspot.com]] with any comments or suggestions."
].join("\n"),
'TspotSidebar':[
"<<upload http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/store.cgi index.html . . " + config.tiddlyspotSiteId + ">><html><a href='http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/download' class='button'>download</a></html>"
].join("\n")
});
//}}}
| !date | !user | !location | !storeUrl | !uploadDir | !toFilename | !backupdir | !origin |
| 14/04/2012 12:02:28 | YourName | [[/|http://edittoc.tiddlyspot.com/]] | [[store.cgi|http://edittoc.tiddlyspot.com/store.cgi]] | . | [[index.html | http://edittoc.tiddlyspot.com/index.html]] | . |
| 14/04/2012 12:17:22 | YourName | [[/|http://edittoc.tiddlyspot.com/]] | [[store.cgi|http://edittoc.tiddlyspot.com/store.cgi]] | . | [[index.html | http://edittoc.tiddlyspot.com/index.html]] | . |
| 14/04/2012 13:09:53 | YourName | [[/|http://edittoc.tiddlyspot.com/]] | [[store.cgi|http://edittoc.tiddlyspot.com/store.cgi]] | . | [[index.html | http://edittoc.tiddlyspot.com/index.html]] | . |
| 14/04/2012 13:12:09 | YourName | [[/|http://edittoc.tiddlyspot.com/]] | [[store.cgi|http://edittoc.tiddlyspot.com/store.cgi]] | . | [[index.html | http://edittoc.tiddlyspot.com/index.html]] | . | ok |
| 14/04/2012 13:12:47 | YourName | [[/|http://edittoc.tiddlyspot.com/]] | [[store.cgi|http://edittoc.tiddlyspot.com/store.cgi]] | . | [[index.html | http://edittoc.tiddlyspot.com/index.html]] | . | ok |
| 14/04/2012 13:17:08 | YourName | [[/|http://edittoc.tiddlyspot.com/]] | [[store.cgi|http://edittoc.tiddlyspot.com/store.cgi]] | . | [[index.html | http://edittoc.tiddlyspot.com/index.html]] | . |
| 21/04/2012 09:15:08 | YourName | [[/|http://edittoc.tiddlyspot.com/]] | [[store.cgi|http://edittoc.tiddlyspot.com/store.cgi]] | . | [[index.html | http://edittoc.tiddlyspot.com/index.html]] | . |
| 21/04/2012 09:19:20 | YourName | [[/|http://edittoc.tiddlyspot.com/]] | [[store.cgi|http://edittoc.tiddlyspot.com/store.cgi]] | . | [[index.html | http://edittoc.tiddlyspot.com/index.html]] | . |
| 21/04/2012 09:48:26 | YourName | [[/|http://edittoc.tiddlyspot.com/]] | [[store.cgi|http://edittoc.tiddlyspot.com/store.cgi]] | . | [[index.html | http://edittoc.tiddlyspot.com/index.html]] | . |
| 21/04/2012 09:50:08 | YourName | [[/|http://edittoc.tiddlyspot.com/]] | [[store.cgi|http://edittoc.tiddlyspot.com/store.cgi]] | . | [[index.html | http://edittoc.tiddlyspot.com/index.html]] | . |
/***
|''Name:''|UploadPlugin|
|''Description:''|Save to web a TiddlyWiki|
|''Version:''|4.1.3|
|''Date:''|Feb 24, 2008|
|''Source:''|http://tiddlywiki.bidix.info/#UploadPlugin|
|''Documentation:''|http://tiddlywiki.bidix.info/#UploadPluginDoc|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0|
|''Requires:''|PasswordOptionPlugin|
***/
//{{{
version.extensions.UploadPlugin = {
major: 4, minor: 1, revision: 3,
date: new Date("Feb 24, 2008"),
source: 'http://tiddlywiki.bidix.info/#UploadPlugin',
author: 'BidiX (BidiX (at) bidix (dot) info',
coreVersion: '2.2.0'
};
//
// Environment
//
if (!window.bidix) window.bidix = {}; // bidix namespace
bidix.debugMode = false; // true to activate both in Plugin and UploadService
//
// Upload Macro
//
config.macros.upload = {
// default values
defaultBackupDir: '', //no backup
defaultStoreScript: "store.php",
defaultToFilename: "index.html",
defaultUploadDir: ".",
authenticateUser: true // UploadService Authenticate User
};
config.macros.upload.label = {
promptOption: "Save and Upload this TiddlyWiki with UploadOptions",
promptParamMacro: "Save and Upload this TiddlyWiki in %0",
saveLabel: "save to web",
saveToDisk: "save to disk",
uploadLabel: "upload"
};
config.macros.upload.messages = {
noStoreUrl: "No store URL in parmeters or options",
usernameOrPasswordMissing: "Username or password missing"
};
config.macros.upload.handler = function(place,macroName,params) {
if (readOnly)
return;
var label;
if (document.location.toString().substr(0,4) == "http")
label = this.label.saveLabel;
else
label = this.label.uploadLabel;
var prompt;
if (params[0]) {
prompt = this.label.promptParamMacro.toString().format([this.destFile(params[0],
(params[1] ? params[1]:bidix.basename(window.location.toString())), params[3])]);
} else {
prompt = this.label.promptOption;
}
createTiddlyButton(place, label, prompt, function() {config.macros.upload.action(params);}, null, null, this.accessKey);
};
config.macros.upload.action = function(params)
{
// for missing macro parameter set value from options
if (!params) params = {};
var storeUrl = params[0] ? params[0] : config.options.txtUploadStoreUrl;
var toFilename = params[1] ? params[1] : config.options.txtUploadFilename;
var backupDir = params[2] ? params[2] : config.options.txtUploadBackupDir;
var uploadDir = params[3] ? params[3] : config.options.txtUploadDir;
var username = params[4] ? params[4] : config.options.txtUploadUserName;
var password = config.options.pasUploadPassword; // for security reason no password as macro parameter
// for still missing parameter set default value
if ((!storeUrl) && (document.location.toString().substr(0,4) == "http"))
storeUrl = bidix.dirname(document.location.toString())+'/'+config.macros.upload.defaultStoreScript;
if (storeUrl.substr(0,4) != "http")
storeUrl = bidix.dirname(document.location.toString()) +'/'+ storeUrl;
if (!toFilename)
toFilename = bidix.basename(window.location.toString());
if (!toFilename)
toFilename = config.macros.upload.defaultToFilename;
if (!uploadDir)
uploadDir = config.macros.upload.defaultUploadDir;
if (!backupDir)
backupDir = config.macros.upload.defaultBackupDir;
// report error if still missing
if (!storeUrl) {
alert(config.macros.upload.messages.noStoreUrl);
clearMessage();
return false;
}
if (config.macros.upload.authenticateUser && (!username || !password)) {
alert(config.macros.upload.messages.usernameOrPasswordMissing);
clearMessage();
return false;
}
bidix.upload.uploadChanges(false,null,storeUrl, toFilename, uploadDir, backupDir, username, password);
return false;
};
config.macros.upload.destFile = function(storeUrl, toFilename, uploadDir)
{
if (!storeUrl)
return null;
var dest = bidix.dirname(storeUrl);
if (uploadDir && uploadDir != '.')
dest = dest + '/' + uploadDir;
dest = dest + '/' + toFilename;
return dest;
};
//
// uploadOptions Macro
//
config.macros.uploadOptions = {
handler: function(place,macroName,params) {
var wizard = new Wizard();
wizard.createWizard(place,this.wizardTitle);
wizard.addStep(this.step1Title,this.step1Html);
var markList = wizard.getElement("markList");
var listWrapper = document.createElement("div");
markList.parentNode.insertBefore(listWrapper,markList);
wizard.setValue("listWrapper",listWrapper);
this.refreshOptions(listWrapper,false);
var uploadCaption;
if (document.location.toString().substr(0,4) == "http")
uploadCaption = config.macros.upload.label.saveLabel;
else
uploadCaption = config.macros.upload.label.uploadLabel;
wizard.setButtons([
{caption: uploadCaption, tooltip: config.macros.upload.label.promptOption,
onClick: config.macros.upload.action},
{caption: this.cancelButton, tooltip: this.cancelButtonPrompt, onClick: this.onCancel}
]);
},
options: [
"txtUploadUserName",
"pasUploadPassword",
"txtUploadStoreUrl",
"txtUploadDir",
"txtUploadFilename",
"txtUploadBackupDir",
"chkUploadLog",
"txtUploadLogMaxLine"
],
refreshOptions: function(listWrapper) {
var opts = [];
for(i=0; i<this.options.length; i++) {
var opt = {};
opts.push();
opt.option = "";
n = this.options[i];
opt.name = n;
opt.lowlight = !config.optionsDesc[n];
opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
opts.push(opt);
}
var listview = ListView.create(listWrapper,opts,this.listViewTemplate);
for(n=0; n<opts.length; n++) {
var type = opts[n].name.substr(0,3);
var h = config.macros.option.types[type];
if (h && h.create) {
h.create(opts[n].colElements['option'],type,opts[n].name,opts[n].name,"no");
}
}
},
onCancel: function(e)
{
backstage.switchTab(null);
return false;
},
wizardTitle: "Upload with options",
step1Title: "These options are saved in cookies in your browser",
step1Html: "<input type='hidden' name='markList'></input><br>",
cancelButton: "Cancel",
cancelButtonPrompt: "Cancel prompt",
listViewTemplate: {
columns: [
{name: 'Description', field: 'description', title: "Description", type: 'WikiText'},
{name: 'Option', field: 'option', title: "Option", type: 'String'},
{name: 'Name', field: 'name', title: "Name", type: 'String'}
],
rowClasses: [
{className: 'lowlight', field: 'lowlight'}
]}
};
//
// upload functions
//
if (!bidix.upload) bidix.upload = {};
if (!bidix.upload.messages) bidix.upload.messages = {
//from saving
invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
backupSaved: "Backup saved",
backupFailed: "Failed to upload backup file",
rssSaved: "RSS feed uploaded",
rssFailed: "Failed to upload RSS feed file",
emptySaved: "Empty template uploaded",
emptyFailed: "Failed to upload empty template file",
mainSaved: "Main TiddlyWiki file uploaded",
mainFailed: "Failed to upload main TiddlyWiki file. Your changes have not been saved",
//specific upload
loadOriginalHttpPostError: "Can't get original file",
aboutToSaveOnHttpPost: 'About to upload on %0 ...',
storePhpNotFound: "The store script '%0' was not found."
};
bidix.upload.uploadChanges = function(onlyIfDirty,tiddlers,storeUrl,toFilename,uploadDir,backupDir,username,password)
{
var callback = function(status,uploadParams,original,url,xhr) {
if (!status) {
displayMessage(bidix.upload.messages.loadOriginalHttpPostError);
return;
}
if (bidix.debugMode)
alert(original.substr(0,500)+"\n...");
// Locate the storeArea div's
var posDiv = locateStoreArea(original);
if((posDiv[0] == -1) || (posDiv[1] == -1)) {
alert(config.messages.invalidFileError.format([localPath]));
return;
}
bidix.upload.uploadRss(uploadParams,original,posDiv);
};
if(onlyIfDirty && !store.isDirty())
return;
clearMessage();
// save on localdisk ?
if (document.location.toString().substr(0,4) == "file") {
var path = document.location.toString();
var localPath = getLocalPath(path);
saveChanges();
}
// get original
var uploadParams = new Array(storeUrl,toFilename,uploadDir,backupDir,username,password);
var originalPath = document.location.toString();
// If url is a directory : add index.html
if (originalPath.charAt(originalPath.length-1) == "/")
originalPath = originalPath + "index.html";
var dest = config.macros.upload.destFile(storeUrl,toFilename,uploadDir);
var log = new bidix.UploadLog();
log.startUpload(storeUrl, dest, uploadDir, backupDir);
displayMessage(bidix.upload.messages.aboutToSaveOnHttpPost.format([dest]));
if (bidix.debugMode)
alert("about to execute Http - GET on "+originalPath);
var r = doHttp("GET",originalPath,null,null,username,password,callback,uploadParams,null);
if (typeof r == "string")
displayMessage(r);
return r;
};
bidix.upload.uploadRss = function(uploadParams,original,posDiv)
{
var callback = function(status,params,responseText,url,xhr) {
if(status) {
var destfile = responseText.substring(responseText.indexOf("destfile:")+9,responseText.indexOf("\n", responseText.indexOf("destfile:")));
displayMessage(bidix.upload.messages.rssSaved,bidix.dirname(url)+'/'+destfile);
bidix.upload.uploadMain(params[0],params[1],params[2]);
} else {
displayMessage(bidix.upload.messages.rssFailed);
}
};
// do uploadRss
if(config.options.chkGenerateAnRssFeed) {
var rssPath = uploadParams[1].substr(0,uploadParams[1].lastIndexOf(".")) + ".xml";
var rssUploadParams = new Array(uploadParams[0],rssPath,uploadParams[2],'',uploadParams[4],uploadParams[5]);
var rssString = generateRss();
// no UnicodeToUTF8 conversion needed when location is "file" !!!
if (document.location.toString().substr(0,4) != "file")
rssString = convertUnicodeToUTF8(rssString);
bidix.upload.httpUpload(rssUploadParams,rssString,callback,Array(uploadParams,original,posDiv));
} else {
bidix.upload.uploadMain(uploadParams,original,posDiv);
}
};
bidix.upload.uploadMain = function(uploadParams,original,posDiv)
{
var callback = function(status,params,responseText,url,xhr) {
var log = new bidix.UploadLog();
if(status) {
// if backupDir specified
if ((params[3]) && (responseText.indexOf("backupfile:") > -1)) {
var backupfile = responseText.substring(responseText.indexOf("backupfile:")+11,responseText.indexOf("\n", responseText.indexOf("backupfile:")));
displayMessage(bidix.upload.messages.backupSaved,bidix.dirname(url)+'/'+backupfile);
}
var destfile = responseText.substring(responseText.indexOf("destfile:")+9,responseText.indexOf("\n", responseText.indexOf("destfile:")));
displayMessage(bidix.upload.messages.mainSaved,bidix.dirname(url)+'/'+destfile);
store.setDirty(false);
log.endUpload("ok");
} else {
alert(bidix.upload.messages.mainFailed);
displayMessage(bidix.upload.messages.mainFailed);
log.endUpload("failed");
}
};
// do uploadMain
var revised = bidix.upload.updateOriginal(original,posDiv);
bidix.upload.httpUpload(uploadParams,revised,callback,uploadParams);
};
bidix.upload.httpUpload = function(uploadParams,data,callback,params)
{
var localCallback = function(status,params,responseText,url,xhr) {
url = (url.indexOf("nocache=") < 0 ? url : url.substring(0,url.indexOf("nocache=")-1));
if (xhr.status == 404)
alert(bidix.upload.messages.storePhpNotFound.format([url]));
if ((bidix.debugMode) || (responseText.indexOf("Debug mode") >= 0 )) {
alert(responseText);
if (responseText.indexOf("Debug mode") >= 0 )
responseText = responseText.substring(responseText.indexOf("\n\n")+2);
} else if (responseText.charAt(0) != '0')
alert(responseText);
if (responseText.charAt(0) != '0')
status = null;
callback(status,params,responseText,url,xhr);
};
// do httpUpload
var boundary = "---------------------------"+"AaB03x";
var uploadFormName = "UploadPlugin";
// compose headers data
var sheader = "";
sheader += "--" + boundary + "\r\nContent-disposition: form-data; name=\"";
sheader += uploadFormName +"\"\r\n\r\n";
sheader += "backupDir="+uploadParams[3] +
";user=" + uploadParams[4] +
";password=" + uploadParams[5] +
";uploaddir=" + uploadParams[2];
if (bidix.debugMode)
sheader += ";debug=1";
sheader += ";;\r\n";
sheader += "\r\n" + "--" + boundary + "\r\n";
sheader += "Content-disposition: form-data; name=\"userfile\"; filename=\""+uploadParams[1]+"\"\r\n";
sheader += "Content-Type: text/html;charset=UTF-8" + "\r\n";
sheader += "Content-Length: " + data.length + "\r\n\r\n";
// compose trailer data
var strailer = new String();
strailer = "\r\n--" + boundary + "--\r\n";
data = sheader + data + strailer;
if (bidix.debugMode) alert("about to execute Http - POST on "+uploadParams[0]+"\n with \n"+data.substr(0,500)+ " ... ");
var r = doHttp("POST",uploadParams[0],data,"multipart/form-data; ;charset=UTF-8; boundary="+boundary,uploadParams[4],uploadParams[5],localCallback,params,null);
if (typeof r == "string")
displayMessage(r);
return r;
};
// same as Saving's updateOriginal but without convertUnicodeToUTF8 calls
bidix.upload.updateOriginal = function(original, posDiv)
{
if (!posDiv)
posDiv = locateStoreArea(original);
if((posDiv[0] == -1) || (posDiv[1] == -1)) {
alert(config.messages.invalidFileError.format([localPath]));
return;
}
var revised = original.substr(0,posDiv[0] + startSaveArea.length) + "\n" +
store.allTiddlersAsHtml() + "\n" +
original.substr(posDiv[1]);
var newSiteTitle = getPageTitle().htmlEncode();
revised = revised.replaceChunk("<title"+">","</title"+">"," " + newSiteTitle + " ");
revised = updateMarkupBlock(revised,"PRE-HEAD","MarkupPreHead");
revised = updateMarkupBlock(revised,"POST-HEAD","MarkupPostHead");
revised = updateMarkupBlock(revised,"PRE-BODY","MarkupPreBody");
revised = updateMarkupBlock(revised,"POST-SCRIPT","MarkupPostBody");
return revised;
};
//
// UploadLog
//
// config.options.chkUploadLog :
// false : no logging
// true : logging
// config.options.txtUploadLogMaxLine :
// -1 : no limit
// 0 : no Log lines but UploadLog is still in place
// n : the last n lines are only kept
// NaN : no limit (-1)
bidix.UploadLog = function() {
if (!config.options.chkUploadLog)
return; // this.tiddler = null
this.tiddler = store.getTiddler("UploadLog");
if (!this.tiddler) {
this.tiddler = new Tiddler();
this.tiddler.title = "UploadLog";
this.tiddler.text = "| !date | !user | !location | !storeUrl | !uploadDir | !toFilename | !backupdir | !origin |";
this.tiddler.created = new Date();
this.tiddler.modifier = config.options.txtUserName;
this.tiddler.modified = new Date();
store.addTiddler(this.tiddler);
}
return this;
};
bidix.UploadLog.prototype.addText = function(text) {
if (!this.tiddler)
return;
// retrieve maxLine when we need it
var maxLine = parseInt(config.options.txtUploadLogMaxLine,10);
if (isNaN(maxLine))
maxLine = -1;
// add text
if (maxLine != 0)
this.tiddler.text = this.tiddler.text + text;
// Trunck to maxLine
if (maxLine >= 0) {
var textArray = this.tiddler.text.split('\n');
if (textArray.length > maxLine + 1)
textArray.splice(1,textArray.length-1-maxLine);
this.tiddler.text = textArray.join('\n');
}
// update tiddler fields
this.tiddler.modifier = config.options.txtUserName;
this.tiddler.modified = new Date();
store.addTiddler(this.tiddler);
// refresh and notifiy for immediate update
story.refreshTiddler(this.tiddler.title);
store.notify(this.tiddler.title, true);
};
bidix.UploadLog.prototype.startUpload = function(storeUrl, toFilename, uploadDir, backupDir) {
if (!this.tiddler)
return;
var now = new Date();
var text = "\n| ";
var filename = bidix.basename(document.location.toString());
if (!filename) filename = '/';
text += now.formatString("0DD/0MM/YYYY 0hh:0mm:0ss") +" | ";
text += config.options.txtUserName + " | ";
text += "[["+filename+"|"+location + "]] |";
text += " [[" + bidix.basename(storeUrl) + "|" + storeUrl + "]] | ";
text += uploadDir + " | ";
text += "[[" + bidix.basename(toFilename) + " | " +toFilename + "]] | ";
text += backupDir + " |";
this.addText(text);
};
bidix.UploadLog.prototype.endUpload = function(status) {
if (!this.tiddler)
return;
this.addText(" "+status+" |");
};
//
// Utilities
//
bidix.checkPlugin = function(plugin, major, minor, revision) {
var ext = version.extensions[plugin];
if (!
(ext &&
((ext.major > major) ||
((ext.major == major) && (ext.minor > minor)) ||
((ext.major == major) && (ext.minor == minor) && (ext.revision >= revision))))) {
// write error in PluginManager
if (pluginInfo)
pluginInfo.log.push("Requires " + plugin + " " + major + "." + minor + "." + revision);
eval(plugin); // generate an error : "Error: ReferenceError: xxxx is not defined"
}
};
bidix.dirname = function(filePath) {
if (!filePath)
return;
var lastpos;
if ((lastpos = filePath.lastIndexOf("/")) != -1) {
return filePath.substring(0, lastpos);
} else {
return filePath.substring(0, filePath.lastIndexOf("\\"));
}
};
bidix.basename = function(filePath) {
if (!filePath)
return;
var lastpos;
if ((lastpos = filePath.lastIndexOf("#")) != -1)
filePath = filePath.substring(0, lastpos);
if ((lastpos = filePath.lastIndexOf("/")) != -1) {
return filePath.substring(lastpos + 1);
} else
return filePath.substring(filePath.lastIndexOf("\\")+1);
};
bidix.initOption = function(name,value) {
if (!config.options[name])
config.options[name] = value;
};
//
// Initializations
//
// require PasswordOptionPlugin 1.0.1 or better
bidix.checkPlugin("PasswordOptionPlugin", 1, 0, 1);
// styleSheet
setStylesheet('.txtUploadStoreUrl, .txtUploadBackupDir, .txtUploadDir {width: 22em;}',"uploadPluginStyles");
//optionsDesc
merge(config.optionsDesc,{
txtUploadStoreUrl: "Url of the UploadService script (default: store.php)",
txtUploadFilename: "Filename of the uploaded file (default: in index.html)",
txtUploadDir: "Relative Directory where to store the file (default: . (downloadService directory))",
txtUploadBackupDir: "Relative Directory where to backup the file. If empty no backup. (default: ''(empty))",
txtUploadUserName: "Upload Username",
pasUploadPassword: "Upload Password",
chkUploadLog: "do Logging in UploadLog (default: true)",
txtUploadLogMaxLine: "Maximum of lines in UploadLog (default: 10)"
});
// Options Initializations
bidix.initOption('txtUploadStoreUrl','');
bidix.initOption('txtUploadFilename','');
bidix.initOption('txtUploadDir','');
bidix.initOption('txtUploadBackupDir','');
bidix.initOption('txtUploadUserName','');
bidix.initOption('pasUploadPassword','');
bidix.initOption('chkUploadLog',true);
bidix.initOption('txtUploadLogMaxLine','10');
// Backstage
merge(config.tasks,{
uploadOptions: {text: "upload", tooltip: "Change UploadOptions and Upload", content: '<<uploadOptions>>'}
});
config.backstageTasks.push("uploadOptions");
//}}}
<!--{{{-->
<div class='toolbar' 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 macro="sectionTOC"></div>
<span macro="foldHeadings closed systemConfig fold"></span>
<div class='tagClear'></div>
<!--}}}-->
//{{{
config.macros.sectionTOC.linkFormat='[[%0|##%0]] <<editSection [[##%0]] [[✏]]>>';
//}}}