diff options
| author | ianb <devnull@localhost> | 2005-06-13 06:18:19 +0000 |
|---|---|---|
| committer | ianb <devnull@localhost> | 2005-06-13 06:18:19 +0000 |
| commit | fc569bdee88033eaf043cf8cd4fea38e8c96ff9a (patch) | |
| tree | 83a29689bab45f88121fb0057db5fb00c6dfc539 | |
| parent | 844ff4d0aba028053971139f33b2b8908836da98 (diff) | |
| download | paste-fc569bdee88033eaf043cf8cd4fea38e8c96ff9a.tar.gz | |
Added new example application (not completely finished), a file browsing application, with lots of Ajax
24 files changed, 1485 insertions, 0 deletions
diff --git a/examples/filebrowser/__init__.py b/examples/filebrowser/__init__.py new file mode 100644 index 0000000..1cb2d1b --- /dev/null +++ b/examples/filebrowser/__init__.py @@ -0,0 +1,4 @@ +from paste.util.thirdparty import add_package + +add_package('ZopePageTemplates') +add_package('ZPTKit') diff --git a/examples/filebrowser/handlers.py b/examples/filebrowser/handlers.py new file mode 100644 index 0000000..6d28d87 --- /dev/null +++ b/examples/filebrowser/handlers.py @@ -0,0 +1,32 @@ +from paste.util.classinit import ClassInitMeta + +handler_exts = {} + +def get_handler(path): + if path.check(dir=True): + ext_type = 'dir' + else: + ext_type = path.ext + return handler_exts.get(ext, handler_exts['*'])(path) + +class Handler(object): + + __metaclass__ = ClassInitMeta + + abstract = True + exts = () + + def __classinit__(cls, new_args): + if cls.abstract and 'abstract' not in new_args: + cls.abstract = False + if not cls.abstract: + for ext in self.exts: + assert ext not in handler_exts, ( + "Handler %s already registered for extension %r; " + "trying to overwrite with %s" + % (handler_exts[ext], ext, cls)) + handler_exts[ext] = cls + + def __init__(self, path): + self.path = path + diff --git a/examples/filebrowser/icons/1downarrow.png b/examples/filebrowser/icons/1downarrow.png Binary files differnew file mode 100644 index 0000000..a0491b6 --- /dev/null +++ b/examples/filebrowser/icons/1downarrow.png diff --git a/examples/filebrowser/icons/1leftarrow.png b/examples/filebrowser/icons/1leftarrow.png Binary files differnew file mode 100644 index 0000000..7e3fda7 --- /dev/null +++ b/examples/filebrowser/icons/1leftarrow.png diff --git a/examples/filebrowser/icons/1rightarrow.png b/examples/filebrowser/icons/1rightarrow.png Binary files differnew file mode 100644 index 0000000..3264e13 --- /dev/null +++ b/examples/filebrowser/icons/1rightarrow.png diff --git a/examples/filebrowser/icons/1uparrow.png b/examples/filebrowser/icons/1uparrow.png Binary files differnew file mode 100644 index 0000000..c68fd87 --- /dev/null +++ b/examples/filebrowser/icons/1uparrow.png diff --git a/examples/filebrowser/icons/editcopy.png b/examples/filebrowser/icons/editcopy.png Binary files differnew file mode 100644 index 0000000..ee19948 --- /dev/null +++ b/examples/filebrowser/icons/editcopy.png diff --git a/examples/filebrowser/icons/editcut.png b/examples/filebrowser/icons/editcut.png Binary files differnew file mode 100644 index 0000000..0407aff --- /dev/null +++ b/examples/filebrowser/icons/editcut.png diff --git a/examples/filebrowser/icons/editpaste.png b/examples/filebrowser/icons/editpaste.png Binary files differnew file mode 100755 index 0000000..0e3ecff --- /dev/null +++ b/examples/filebrowser/icons/editpaste.png diff --git a/examples/filebrowser/icons/remove.png b/examples/filebrowser/icons/remove.png Binary files differnew file mode 100644 index 0000000..d65de9e --- /dev/null +++ b/examples/filebrowser/icons/remove.png diff --git a/examples/filebrowser/js-lib/common.js b/examples/filebrowser/js-lib/common.js new file mode 100644 index 0000000..a7e52c7 --- /dev/null +++ b/examples/filebrowser/js-lib/common.js @@ -0,0 +1,57 @@ +Element.hidden = function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + if (element.style.display != 'none') { + return false; + } + } + return true; +} + +function lazyToggle(container, url, options) { + options = ({method: 'get'}).extend(options); + if (Element.hidden(container)) { + Element.show(container); + if (! $(container).lazy_loaded) { + new Ajax.Updater(container, url, {method: 'get'}); + $(container).lazy_loaded = true; + } + if (options.link) { + var newHTML = options.link.getAttribute('shownhtml'); + if (newHTML) { + options.link.setAttribute('hiddenhtml', options.link.innerHTML); + options.link.innerHTML = newHTML; + } + } + } else { + Element.hide(container); + if (options.link) { + var newHTML = options.link.getAttribute('hiddenhtml'); + if (newHTML) { + options.link.innerHTML = newHTML; + } + } + } +} + +function toggle(container, options) { + if (Element.hidden(container)) { + Element.show(container); + if (options && options.link) { + var newHTML = options.link.getAttribute('shownhtml'); + if (newHTML) { + options.link.setAttribute('hiddenhtml', options.link.innerHTML); + options.link.innerHTML = newHTML; + } + } + } else { + Element.hide(container); + if (options && options.link) { + var newHTML = options.link.getAttribute('hiddenhtml'); + if (newHTML) { + options.link.setAttribute('shownhtml', options.link.innerHTML); + options.link.innerHTML = newHTML; + } + } + } +} diff --git a/examples/filebrowser/js-lib/prototype-1.2.0.js b/examples/filebrowser/js-lib/prototype-1.2.0.js new file mode 100644 index 0000000..c872152 --- /dev/null +++ b/examples/filebrowser/js-lib/prototype-1.2.0.js @@ -0,0 +1,767 @@ +/* Prototype: an object-oriented Javascript library, version 1.2.0 + * (c) 2005 Sam Stephenson <sam@conio.net> + * + * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff + * against the source tree, available from the Prototype darcs repository. + * + * Prototype is freely distributable under the terms of an MIT-style license. + * + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.2.0' +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.prototype.extend = function(object) { + for (property in object) { + this[property] = object[property]; + } + return this; +} + +Function.prototype.bind = function(object) { + var method = this; + return function() { + method.apply(object, arguments); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var method = this; + return function(event) { + method.call(object, event || window.event); + } +} + +Number.prototype.toColorPart = function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; +} + +var Try = { + these: function() { + var returnValue; + + for (var i = 0; i < arguments.length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(); + } finally { + this.currentlyExecuting = false; + } + } + + this.registerCallback(); + } +} + +/*--------------------------------------------------------------------------*/ + +function $() { + var elements = new Array(); + + for (var i = 0; i < arguments.length; i++) { + var element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + + if (arguments.length == 1) + return element; + + elements.push(element); + } + + return elements; +} + +/*--------------------------------------------------------------------------*/ + +if (!Array.prototype.push) { + Array.prototype.push = function() { + var startLength = this.length; + for (var i = 0; i < arguments.length; i++) + this[startLength + i] = arguments[i]; + return this.length; + } +} + +if (!Function.prototype.apply) { + // Based on code from http://www.youngpup.net/ + Function.prototype.apply = function(object, parameters) { + var parameterStrings = new Array(); + if (!object) object = window; + if (!parameters) parameters = new Array(); + + for (var i = 0; i < parameters.length; i++) + parameterStrings[i] = 'x[' + i + ']'; + + object.__apply__ = this; + var result = eval('obj.__apply__(' + + parameterStrings[i].join(', ') + ')'); + object.__apply__ = null; + + return result; + } +} + +/*--------------------------------------------------------------------------*/ + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')}, + function() {return new XMLHttpRequest()} + ) || false; + }, + + emptyFunction: function() {} +} + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + parameters: '' + }.extend(options || {}); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = (new Ajax.Base()).extend({ + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + + try { + if (this.options.method == 'get') + url += '?' + this.options.parameters + '&_='; + + this.transport.open(this.options.method, url, true); + + if (this.options.asynchronous) { + this.transport.onreadystatechange = this.onStateChange.bind(this); + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + } + + this.transport.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + this.transport.setRequestHeader('X-Prototype-Version', Prototype.Version); + + if (this.options.method == 'post') { + this.transport.setRequestHeader('Connection', 'close'); + this.transport.setRequestHeader('Content-type', + 'application/x-www-form-urlencoded'); + } + + this.transport.send(this.options.method == 'post' ? + this.options.parameters + '&_=' : null); + + } catch (e) { + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + (this.options['on' + event] || Ajax.emptyFunction)(this.transport); + } +}); + +Ajax.Updater = Class.create(); +Ajax.Updater.prototype = (new Ajax.Base()).extend({ + initialize: function(container, url, options) { + this.container = $(container); + if (! this.container) { + alert('Container ' + container + ' does not exist'); + } + this.setOptions(options); + + if (this.options.asynchronous) { + this.onComplete = this.options.onComplete; + this.options.onComplete = this.updateContent.bind(this); + } + + this.request = new Ajax.Request(url, this.options); + + if (!this.options.asynchronous) + this.updateContent(); + }, + + updateContent: function() { + if (this.options.insertion) { + new this.options.insertion(this.container, + this.request.transport.responseText); + } else { + this.container.innerHTML = this.request.transport.responseText; + } + + if (this.onComplete) { + setTimeout((function() {this.onComplete(this.request)}).bind(this), 10); + } + } +}); + +/*--------------------------------------------------------------------------*/ + +var Field = { + clear: function() { + for (var i = 0; i < arguments.length; i++) + $(arguments[i]).value = ''; + }, + + focus: function(element) { + $(element).focus(); + }, + + present: function() { + for (var i = 0; i < arguments.length; i++) + if ($(arguments[i]).value == '') return false; + return true; + }, + + select: function(element) { + $(element).select(); + }, + + activate: function(element) { + $(element).focus(); + $(element).select(); + } +} + +/*--------------------------------------------------------------------------*/ + +var Form = { + serialize: function(form) { + var elements = Form.getElements($(form)); + var queryComponents = new Array(); + + for (var i = 0; i < elements.length; i++) { + var queryComponent = Form.Element.serialize(elements[i]); + if (queryComponent) + queryComponents.push(queryComponent); + } + + return queryComponents.join('&'); + }, + + getElements: function(form) { + form = $(form); + var elements = new Array(); + + for (tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + return elements; + }, + + disable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disable = 'true'; + } + }, + + focusFirstElement: function(form) { + form = $(form); + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + if (element.type != 'hidden' && !element.disabled) { + Field.activate(element); + break; + } + } + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return encodeURIComponent(parameter[0]) + '=' + + encodeURIComponent(parameter[1]); + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + } +} + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'hidden': + case 'password': + case 'text': + return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + select: function(element) { + var index = element.selectedIndex; + var value = element.options[index].value || element.options[index].text; + return [element.name, (index >= 0) ? value : '']; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + + this.registerCallback(); + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ + getValue: function() { + return Form.serialize(this.element); + } +}); + + +/*--------------------------------------------------------------------------*/ + +document.getElementsByClassName = function(className) { + var children = document.getElementsByTagName('*') || document.all; + var elements = new Array(); + + for (var i = 0; i < children.length; i++) { + var child = children[i]; + var classNames = child.className.split(' '); + for (var j = 0; j < classNames.length; j++) { + if (classNames[j] == className) { + elements.push(child); + break; + } + } + } + + return elements; +} + +/*--------------------------------------------------------------------------*/ + +var Element = { + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = + (element.style.display == 'none' ? '' : 'none'); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + } +} + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content; + + if (this.adjacency && this.element.insertAdjacentHTML) { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.fragment = this.range.createContextualFragment(this.content); + this.insertContent(); + } + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({ + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function() { + this.element.parentNode.insertBefore(this.fragment, this.element); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({ + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function() { + this.element.insertBefore(this.fragment, this.element.firstChild); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({ + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function() { + this.element.appendChild(this.fragment); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function() { + this.element.parentNode.insertBefore(this.fragment, + this.element.nextSibling); + } +}); + +/*--------------------------------------------------------------------------*/ + +var Effect = new Object(); + +Effect.Highlight = Class.create(); +Effect.Highlight.prototype = { + initialize: function(element) { + this.element = $(element); + this.start = 153; + this.finish = 255; + this.current = this.start; + this.fade(); + }, + + fade: function() { + if (this.isFinished()) return; + if (this.timer) clearTimeout(this.timer); + this.highlight(this.element, this.current); + this.current += 17; + this.timer = setTimeout(this.fade.bind(this), 250); + }, + + isFinished: function() { + return this.current > this.finish; + }, + + highlight: function(element, current) { + element.style.backgroundColor = "#ffff" + current.toColorPart(); + } +} + + +Effect.Fade = Class.create(); +Effect.Fade.prototype = { + initialize: function(element) { + this.element = $(element); + this.start = 100; + this.finish = 0; + this.current = this.start; + this.fade(); + }, + + fade: function() { + if (this.isFinished()) { this.element.style.display = 'none'; return; } + if (this.timer) clearTimeout(this.timer); + this.setOpacity(this.element, this.current); + this.current -= 10; + this.timer = setTimeout(this.fade.bind(this), 50); + }, + + isFinished: function() { + return this.current <= this.finish; + }, + + setOpacity: function(element, opacity) { + opacity = (opacity == 100) ? 99.999 : opacity; + element.style.filter = "alpha(opacity:"+opacity+")"; + element.style.opacity = opacity/100 /*//*/; + } +} + +Effect.Scale = Class.create(); +Effect.Scale.prototype = { + initialize: function(element, percent) { + this.element = $(element); + this.startScale = 1.0; + this.startHeight = this.element.offsetHeight; + this.startWidth = this.element.offsetWidth; + this.currentHeight = this.startHeight; + this.currentWidth = this.startWidth; + this.finishScale = (percent/100) /*//*/; + if (this.element.style.fontSize=="") this.sizeEm = 1.0; + if (this.element.style.fontSize.indexOf("em")>0) + this.sizeEm = parseFloat(this.element.style.fontSize); + if(this.element.effect_scale) { + clearTimeout(this.element.effect_scale.timer); + this.startScale = this.element.effect_scale.currentScale; + this.startHeight = this.element.effect_scale.startHeight; + this.startWidth = this.element.effect_scale.startWidth; + if(this.element.effect_scale.sizeEm) + this.sizeEm = this.element.effect_scale.sizeEm; + } + this.element.effect_scale = this; + this.currentScale = this.startScale; + this.factor = this.finishScale - this.startScale; + this.options = arguments[2] || {}; + this.scale(); + }, + + scale: function() { + if (this.isFinished()) { + this.setDimensions(this.element, this.startWidth*this.finishScale, this.startHeight*this.finishScale); + if(this.sizeEm) this.element.style.fontSize = this.sizeEm*this.finishScale + "em"; + if(this.options.complete) this.options.complete(this); + return; + } + if (this.timer) clearTimeout(this.timer); + if (this.options.step) this.options.step(this); + this.setDimensions(this.element, this.currentWidth, this.currentHeight); + if(this.sizeEm) this.element.style.fontSize = this.sizeEm*this.currentScale + "em"; + this.currentScale += (this.factor/10) /*//*/; + this.currentWidth = this.startWidth * this.currentScale; + this.currentHeight = this.startHeight * this.currentScale; + this.timer = setTimeout(this.scale.bind(this), 50); + }, + + isFinished: function() { + return (this.factor < 0) ? + this.currentScale <= this.finishScale : this.currentScale >= this.finishScale; + }, + + setDimensions: function(element, width, height) { + element.style.width = width + 'px'; + element.style.height = height + 'px'; + } +} + +Effect.Squish = Class.create(); +Effect.Squish.prototype = { + initialize: function(element) { + this.element = $(element); + new Effect.Scale(this.element, 1, { complete: this.hide.bind(this) } ); + }, + hide: function() { + this.element.style.display = 'none'; + } +} + +Effect.Puff = Class.create(); +Effect.Puff.prototype = { + initialize: function(element) { + this.element = $(element); + this.opacity = 100; + this.startTop = this.element.top || this.element.offsetTop; + this.startLeft = this.element.left || this.element.offsetLeft; + new Effect.Scale(this.element, 200, { step: this.fade.bind(this), complete: this.hide.bind(this) } ); + }, + fade: function(effect) { + topd = (((effect.currentScale)*effect.startHeight) - effect.startHeight)/2; + leftd = (((effect.currentScale)*effect.startWidth) - effect.startWidth)/2; + this.element.style.position='absolute'; + this.element.style.top = this.startTop-topd + "px"; + this.element.style.left = this.startLeft-leftd + "px"; + this.opacity -= 10; + this.setOpacity(this.element, this.opacity); + if(navigator.appVersion.indexOf('AppleWebKit')>0) this.element.innerHTML += ''; //force redraw on safari + }, + hide: function() { + this.element.style.display = 'none'; + }, + setOpacity: function(element, opacity) { + opacity = (opacity == 100) ? 99.999 : opacity; + element.style.filter = "alpha(opacity:"+opacity+")"; + element.style.opacity = opacity/100 /*//*/; + } +} + +Effect.Appear = Class.create(); +Effect.Appear.prototype = { + initialize: function(element) { + this.element = $(element); + this.start = 0; + this.finish = 100; + this.current = this.start; + this.fade(); + }, + + fade: function() { + if (this.isFinished()) return; + if (this.timer) clearTimeout(this.timer); + this.setOpacity(this.element, this.current); + this.current += 10; + this.timer = setTimeout(this.fade.bind(this), 50); + }, + + isFinished: function() { + return this.current > this.finish; + }, + + setOpacity: function(element, opacity) { + opacity = (opacity == 100) ? 99.999 : opacity; + element.style.filter = "alpha(opacity:"+opacity+")"; + element.style.opacity = opacity/100 /*//*/; + element.style.display = ''; + } +} + +Effect.ContentZoom = Class.create(); +Effect.ContentZoom.prototype = { + initialize: function(element, percent) { + this.element = $(element); + if (this.element.style.fontSize=="") this.sizeEm = 1.0; + if (this.element.style.fontSize.indexOf("em")>0) + this.sizeEm = parseFloat(this.element.style.fontSize); + if(this.element.effect_contentzoom) { + this.sizeEm = this.element.effect_contentzoom.sizeEm; + } + this.element.effect_contentzoom = this; + this.element.style.fontSize = this.sizeEm*(percent/100) + "em" /*//*/; + if(navigator.appVersion.indexOf('AppleWebKit')>0) { this.element.scrollTop -= 1; }; + } +} diff --git a/examples/filebrowser/server.conf b/examples/filebrowser/server.conf new file mode 100644 index 0000000..7a0dd1b --- /dev/null +++ b/examples/filebrowser/server.conf @@ -0,0 +1,28 @@ +# -*- python -*- Note: this file is in Python syntax + +import os + +app_template = 'wareweb_zpt' +app_name = 'filebrowser' +framework = 'wareweb' +root_path = '/home/ianb/svn.w4py.org/Paste/trunk/examples/filebrowser' +publish_dir = os.path.join(root_path, 'web') +sys_path.extend([ + os.path.dirname(root_path), + '/home/ianb/co/py-lib', + '/home/ianb/co/ZPTKit', + ]) + +## Server options: + +verbose = False +# The name of the server-type to start: +server = 'wsgiutils' +# If true, files will be regularly polled and the server restarted +# if files are modified: +reload = True +# If true, tracebacks will be shown in the browser: +debug = True + +browse_path = os.path.join(root_path, 'test-data') +#browse_path = '/home/ianb/media/music/artist' diff --git a/examples/filebrowser/sitepage.py b/examples/filebrowser/sitepage.py new file mode 100644 index 0000000..10ac1a5 --- /dev/null +++ b/examples/filebrowser/sitepage.py @@ -0,0 +1,243 @@ +import cgi +import itertools +import os +import urllib +from paste.wareweb import * +from paste import wsgilib +from ZPTKit.zptwareweb import ZPTComponent +from py.path import local +import handlers + +class SitePage(Servlet): + + message = Notify() + template = ZPTComponent() + app_name = 'filebrowser' + + def awake(self): + super(SitePage, self).awake(call_setup=False) + self.root = local(self.config['browse_path']) + if 'browser.filepath' in self.environ: + self.pathinfo = self.environ['browser.filepath'] + self.path = self.root.join(self.pathinfo) + else: + self.pathinfo = '' + self.path = None + self.pathurl = URL(self.environ, + script_name=self.app_url, + path_info=self.pathinfo) + self.globalurl = URL(self.environ, + script_name=self.app_url + '/_app', + path_info='') + self.icons = Icon(self.app_url + '/_icons') + self.app_static_url = self.app_url + '/_static' + self.fragment = '_' in self.fields + self.copybin = self.session.setdefault('copybin', {}) + self.update_copybin_display() + self.setup() + + def rootedpath(self, path): + path = str(path) + assert path.startswith(str(self.root)), ( + "Bad path: %r (doesn't start with %r)" % (path, self.root)) + return path[len(str(self.root)):] + + def handler(self, path=None): + path = path or self.file + handler = handlers.get_handler(path) + + def update_copybin_display(self): + files = self.options.copyfiles = [] + for filename, cutcopy in self.copybin.items(): + path = local(self.root).join(filename) + files.append( + {'filename': filename, + 'url': self.app_url + filename, + 'path': path, + 'name': path.basename, + 'cut': cutcopy=='cut', + 'copy': cutcopy=='copy', + 'id': self.pathid('copy_', filename), + }) + files.sort(key=lambda x: x['filename']) + + def pathid(self, prefix, path): + if isinstance(path, local): + path = self.rootedpath(path) + return prefix + path.replace('/', '__') + + def copybutton(self, path, copytype='copy'): + assert copytype in ('copy', 'cut') + copyurl = self.globalurl('copybin', **{ + copytype: self.rootedpath(path)}) + remote = copyurl.remote( + id='copybin', + onComplete='function (req) {new Effect.Highlight(%r)}' + % self.pathid('copy_', path)) + img = self.icons('edit%s.png' % copytype, alt=copytype, + title=copytype) + return remote.link(img) + + def pastebutton(self, path): + copyurl = self.globalurl('copybin', paste=self.rootedpath(path), + back=wsgilib.construct_url(self.environ)) + return '<a href="%s">%s</a>' % ( + copyurl, self.icons('editpaste', alt="paste", + title="Paste Files")) + + +class URL(object): + + def __init__(self, environ, script_name=None, path_info=None, + vars=None): + if script_name is None: + script_name = environ['SCRIPT_NAME'] + self.script_name = script_name + if path_info is None: + path_info = environ['PATH_INFO'] + self.path_info = path_info + self.environ = environ + self.vars = vars or {} + + def __str__(self): + return wsgilib.construct_url( + self.environ, with_query_string=True, with_path_info=True, + script_name=self.script_name, path_info=self.path_info, + querystring=urllib.urlencode(self.vars)) + + def __repr__(self): + return '<URL %s>' % self + + def url_without_qs(self): + return wsgilib.construct_url( + self.environ, with_query_string=True, with_path_info=True, + script_name=self.script_name, path_info=self.path_info, + querystring='') + url_without_qs = property(url_without_qs) + + def __getitem__(self, add): + vars = self.vars.copy() + path_info = self.path_info + if add and '=' in add: + name, value = add.split('=', 1) + vars[name] = value + elif add: + if not path_info.endswith('/'): + path_info += '/' + path_info += add + return self.__class__(self.environ, script_name=self.script_name, + path_info=path_info, vars=vars) + + def __call__(self, *args, **kw): + obj = self[None] + for arg in args: + obj = obj[arg] + for name, value in kw.items(): + obj.vars[name] = value + return obj + + def remote(self, id=None, **options): + if id is None: + id = idgen() + return JSRemote(self, id, options) + + def toggle(self, id=None, **options): + if id is None: + id = idgen() + return JSToggle(self, id, options) + + def up(self): + path_info = '/'.join(self.path_info.split('/')[:-1]) + return self.__class__( + self.environ, script_name=self.script_name, + path_info=path_info, vars=self.vars) + + def name(self): + return self.path_info.split('/')[-1] + +_idgen_count = itertools.count() +def idgen(): + return 'node%s' % hex(_idgen_count.next() % 0xffffff)[2:] + +class JSRemote(object): + + def __init__(self, url, id, options): + self.url = url + self.id = id + self.options = options + + def option_js(self, **base): + base.update(self.options) + return '{%s}' % (', '.join([ + '%s: %s' % (name, value) + for name, value in base.items()])) + + def javascript(self): + return "new Ajax.Updater(%r, %r, %s); return false" % ( + self.id, self.url.url_without_qs, + self.option_js(parameters=repr(urllib.urlencode(self.url.vars.items())), + method=repr('get'))) + + javascript = property(javascript) + + def link(self, description, structure=True, **kw): + if not structure: + description = cgi.escape(description, 1) + return '<a href="%s" onclick="%s"%s>%s</a>' % ( + cgi.escape(str(self.url), 1), cgi.escape(self.javascript, 1), + html_attrs(kw), description) + +class JSToggle(JSRemote): + + def javascript(self): + return "lazyToggle(%r, %r, {link: this, parameters: %r}); return false" % ( + self.id, self.url.url_without_qs, + self.option_js(parameters=repr(urllib.urlencode(self.url.vars.items())), + link='this')) + + javascript = property(javascript) + +def html_attrs(kw): + if not kw: + return '' + return ''.join([ + ' %s="%s"' % (name, cgi.escape(str(value), 1)) + for name, value in kw.items()]) + +class Icon(object): + + def __init__(self, url, attrs=None): + self.url = url + self.attrs = attrs or {} + + def html__get(self): + return '<img src="%s" border=0%s>' % ( + self.url, html_attrs(self.attrs)) + html = property(html__get) + + __str__ = html__get + + def copy(self): + return self.__class__(self.url, self.attrs.copy()) + + def __call__(self, *names, **kw): + obj = self.copy() + for name in names: + obj = obj[name] + obj.attrs.update(kw) + return obj + + def __getitem__(self, icon_name): + if '=' in icon_name: + obj = self.copy() + name, value = icon_name.split('=', 1) + value = value.replace('+', ' ') + obj.attrs[name] = value + return obj + else: + return self.__class__(self.url + '/' + icon_name) + +# This protects "from sitepage import *", since we will no longer +# need these variables: +del ZPTComponent +del Notify diff --git a/examples/filebrowser/static/stylesheet.css b/examples/filebrowser/static/stylesheet.css new file mode 100644 index 0000000..5560993 --- /dev/null +++ b/examples/filebrowser/static/stylesheet.css @@ -0,0 +1,43 @@ +body { + font-family: Helvetica, Arial, sans-serif; +} + +fieldset#copybinframe { + width: 40%; + float: right; + padding: 3px; +} + +td { + vertical-align: top; +} + +div.controls { + float: right; + display: inline; +} + +blockquote.subdisplay { + margin-top: 0; + margin-bottom: 0; + margin-right: 0; +} + +div.item { + margin-top: 0; + margin-bottom: 0; +} + +div.odd { + background-color: #eee; +} + +pre { + overflow: auto; +} + +div.notifyMessage { + border: 2px solid black; + background-color: #007700; + color: #ffffff; +} diff --git a/examples/filebrowser/templates/copybin.pt b/examples/filebrowser/templates/copybin.pt new file mode 100644 index 0000000..a2d011c --- /dev/null +++ b/examples/filebrowser/templates/copybin.pt @@ -0,0 +1,28 @@ +<metal:tpl define-macro="copybin"> + +<tal:if condition="options/copyfiles"> + +<tal:for repeat="f options/copyfiles"> + <div tal:attributes="class python: 'item %s' % test(repeat['f'].odd(), + 'odd', 'even'); + id f/id"> + <div class="controls"> + <span tal:replace="structure python: servlet.globalurl('copybin', + remove=f['filename']).remote('copybin').link(servlet.icons( + 'remove.png', title='Remove from clipboard'))"/> + </div> + <a tal:attributes="href f/url" + tal:content="f/filename" /> + <tal:if condition="f/cut">(cut)</tal:if> + </div> +</tal:for> + +<span style="float: right" + tal:content="structure python: servlet.globalurl('copybin', clear='t').remote('copybin').link('clear')"/> +</tal:if> + +<tal:if condition="not: options/copyfiles"> +the bin is empty +</tal:if> + +</metal:tpl> diff --git a/examples/filebrowser/templates/directory.pt b/examples/filebrowser/templates/directory.pt new file mode 100644 index 0000000..6215086 --- /dev/null +++ b/examples/filebrowser/templates/directory.pt @@ -0,0 +1,42 @@ +<html metal:use-macro="here/standard_template.pt/macros/page"> +<metal:body fill-slot="body"> + +<div class="header"> + <div class="controls"> + <span tal:replace="structure python: + servlet.pastebutton(servlet.path)"/> + </div> + <tal:if condition="python: not servlet.fragment and options.get('parent')"> + Parent: <a tal:attributes="href options/parent/url" + tal:content="options/parent/name"/><br> + </tal:if> + Files: <span tal:replace="python: len(options['files'])"/> +</div> + +<tal:for repeat="f options/files"> + <div tal:attributes="class python: 'item %s' % test(repeat['f'].odd(), + 'odd', 'even')" + tal:define="toggle f/url/toggle"> + <div class="controls"> + <span tal:condition="python: f['path'].check(dir=1)" + tal:replace="structure python: servlet.pastebutton(f['path'])"/> + <span tal:replace="structure python: servlet.copybutton(f['path'], 'cut')"/> + <span tal:replace="structure python: servlet.copybutton(f['path'], + 'copy')"/> + </div> + <span tal:replace="structure python: toggle.link( + servlet.icons('1downarrow.png', alt='display'), + shownhtml=servlet.icons('1uparrow.png', alt='hide'))"/> + <a tal:attributes="href f/url" + tal:content="f/name" /> + <blockquote tal:attributes="id toggle/id" + style="display: none" class="subdisplay">...</blockquote> + </div> +</tal:for> + +<p tal:condition="not: options/files"> +There are no files in this directory +</p> + +</metal:body> +</html>
\ No newline at end of file diff --git a/examples/filebrowser/templates/generic_error.pt b/examples/filebrowser/templates/generic_error.pt new file mode 100644 index 0000000..7b32820 --- /dev/null +++ b/examples/filebrowser/templates/generic_error.pt @@ -0,0 +1,7 @@ +<html metal:use-macro="here/standard_template.pt/macros/page"> +<metal:body fill-slot="body"> + +<tal:block replace="structure options/error_message" /> + +</metal:body> +</html>
\ No newline at end of file diff --git a/examples/filebrowser/templates/standard_template.pt b/examples/filebrowser/templates/standard_template.pt new file mode 100644 index 0000000..9cf589a --- /dev/null +++ b/examples/filebrowser/templates/standard_template.pt @@ -0,0 +1,55 @@ +<metal:tpl define-macro="page"> +<tal:if condition="not: servlet/fragment"> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" + "http://www.w3.org/TR/REC-html40/loose.dtd"> +<html lang="en-US"> +<head> +<title tal:content="servlet/title">title</title> +<link rel="stylesheet" type="text/css" + tal:attributes="href python: servlet.app_static_url + '/stylesheet.css'"> +<script type="text/javascript" + tal:attributes="src string: ${servlet/app_url}/_js-lib/prototype-1.2.0.js"> +</script> +<script type="text/javascript" + tal:attributes="src string: ${servlet/app_url}/_js-lib/common.js"> +</script> + +<metal:slot define-slot="extra_head"></metal:slot> + +</head> +<body> + +<fieldset id="copybinframe"> +<legend>clipboard <a onclick="toggle('copybin', {link: this})" + tal:attributes="hiddenhtml + servlet/icons/1uparrow.png/alt=show/title=show+clipboard/html" + tal:content="structure servlet/icons/1downarrow.png/alt=hide/title=hide+clipboard/html"/></legend> +<div id="copybin"> +<metal:tpl use-macro="here/copybin.pt/macros/copybin"> +</metal:tpl> +</div> +</fieldset> + +<h1><tal:mark replace="servlet/html_title"/></h1> + +<div class="notifyMessage" + tal:condition="servlet/message" + tal:content="structure servlet/message/html"> + [This is where the notification messages go] +</div> + +<div id="content"> +<metal:tpl define-slot="body"> +[This page has not customized its "body" slot] +</metal:tpl> +</div> + +</body> +</html> +</tal:if> +<tal:if condition="servlet/fragment"> +<metal:tpl define-slot="body"> +[This page has not customized its "body" slot] +</metal:tpl> +</tal:if> +</metal:tpl> diff --git a/examples/filebrowser/templates/view_file.pt b/examples/filebrowser/templates/view_file.pt new file mode 100644 index 0000000..f0904df --- /dev/null +++ b/examples/filebrowser/templates/view_file.pt @@ -0,0 +1,26 @@ +<html metal:use-macro="here/standard_template.pt/macros/page"> +<metal:body fill-slot="body"> + +<p> +<tal:if condition="not: servlet/fragment"> +<tal:if condition="options/parent | nothing"> +Parent: <a tal:attributes="href options/parent/url" + tal:content="options/parent/name">name</a><br> +</tal:if> +</tal:if> +Mimetype: <span tal:replace="options/mimetype"/><br> +<a tal:attributes="href servlet/pathurl/action=download">download</a> +</p> + +<pre tal:condition="python: options['content'] and not options['use_iframe']" + tal:content="options/content">content</pre> + +<img tal:condition="options/is_image" + tal:attributes="src servlet/pathurl/action=download"> + +<iframe tal:condition="options/use_iframe" + tal:attributes="src servlet/pathurl/action=download/html=clean" + width="100%" height="200" name="test"></iframe> + +</metal:body> +</html>
\ No newline at end of file diff --git a/examples/filebrowser/web/__init__.py b/examples/filebrowser/web/__init__.py new file mode 100644 index 0000000..0ffdb8b --- /dev/null +++ b/examples/filebrowser/web/__init__.py @@ -0,0 +1,39 @@ +import os +import urllib +from py.path import local +from paste import wsgilib +from paste import httpexceptions +from paste import urlparser + +def urlparser_hook(environ): + if not environ.has_key('filebrowser.base_url'): + environ['filebrowser.base_url'] = environ['SCRIPT_NAME'] + +special_dirs = {} + +for special_dir in ['js-lib', 'icons', 'static']: + special_dirs[special_dir] = urlparser.URLParser( + os.path.join(os.path.dirname(os.path.dirname(__file__)), + special_dir), None) + +special_dirs['app'] = urlparser.URLParser( + os.path.dirname(__file__), 'filebrowser.web') + +def application(environ, start_response): + if environ.get('browser.resolved'): + return special_dirs['app'](environ, start_response) + conf = environ['paste.config'] + path_info = environ.get('PATH_INFO', '') + for special_dir, parser in special_dirs.items(): + if path_info.startswith('/_%s/' % special_dir): + environ['browser.resolved'] = True + wsgilib.path_info_pop(environ) + return parser(environ, start_response) + environ['browser.filepath'] = urllib.unquote(environ['PATH_INFO']) + vars = dict(wsgilib.parse_querystring(environ)) + action = (vars.get('action') or ['index'])[0] + servlet_fn = os.path.join(os.path.dirname(__file__), action + '.py') + servlet_app = urlparser.make_py(environ, servlet_fn) + return servlet_app(environ, start_response) + + diff --git a/examples/filebrowser/web/copybin.py b/examples/filebrowser/web/copybin.py new file mode 100644 index 0000000..b17153f --- /dev/null +++ b/examples/filebrowser/web/copybin.py @@ -0,0 +1,54 @@ +from filebrowser.sitepage import * +from py.path import local +import py + +class copybin(SitePage): + + bins = ['copy', 'cut'] + + def setup(self): + for fn in self.fields.getlist('copy'): + self.copybin[fn] = 'copy' + for fn in self.fields.getlist('cut'): + self.copybin[fn] = 'cut' + for fn in self.fields.getlist('remove'): + if fn in self.copybin: + del self.copybin[fn] + if self.fields.get('clear'): + self.copybin.clear() + if 'paste' in self.fields: + return self.paste() + self.update_copybin_display() + + def paste(self): + if not self.copybin: + self.message.write('No items to paste') + self.redirect(self.fields.back) + return + dest = self.root.join(self.fields.paste) + copied = 0 + moved = 0 + for filename, copytype in self.copybin.items(): + path = self.root.join(filename) + if copytype == 'copy': + copied += 1 + path.copy(dest) + elif copytype == 'cut': + try: + path.move(dest) + except py.error.ENOTEMPTY, e: + self.message.write( + 'Cannot move %s (file by the same name exists' % dest) + else: + moved += 1 + msg = [] + if copied: + msg.append('%i file%s copied' % ( + copied, copied>1 and 's' or '')) + if moved: + msg.append('%i file%s moved' % ( + moved, moved>1 and 's' or '')) + msg = ', '.join(msg) + self.copybin.clear() + self.message.write('%s to %s' % (msg, self.fields.paste)) + self.redirect(self.fields.back) diff --git a/examples/filebrowser/web/download.py b/examples/filebrowser/web/download.py new file mode 100644 index 0000000..a18ebf6 --- /dev/null +++ b/examples/filebrowser/web/download.py @@ -0,0 +1,20 @@ +import mimetypes +import re +from filebrowser.sitepage import SitePage + +bad_regexes = [ + re.compile(r'<script.*?</script>', re.I+re.S), + re.compile(r'style="[^"]*position:.*?"', re.I+re.S), + ] + +class download(SitePage): + + def setup(self): + mime, _ = mimetypes.guess_type(str(self.path)) + self.set_header('Content-type', mime) + self.view = None + content = self.path.read() + if self.fields.get('html') == 'clean': + for bad_regex in bad_regexes: + content = bad_regex.sub('', content) + self.write(content) diff --git a/examples/filebrowser/web/index.py b/examples/filebrowser/web/index.py new file mode 100644 index 0000000..2a80712 --- /dev/null +++ b/examples/filebrowser/web/index.py @@ -0,0 +1,40 @@ +import mimetypes +from filebrowser.sitepage import SitePage + +class index(SitePage): + + def setup(self): + self.title = 'File: %s' % self.path.basename + self.options.parent = { + 'url': self.pathurl.up(), + 'name': self.pathurl.up().name() or 'root', + } + if self.path.check(dir=1): + self.setup_dir() + else: + self.setup_file() + + def setup_dir(self): + files = [] + for f in self.path.listdir(sort=True): + files.append({ + 'path': f, + 'name': f.basename, + 'url': self.pathurl[f.basename], + 'copyid': self.pathid('copy_', f), + }) + if f.check(dir=1): + files[-1]['name'] += '/' + self.options.files = files + self.view = 'directory.pt' + + def setup_file(self): + self.options.mimetype, _ = mimetypes.guess_type(str(self.path)) + mime = self.options.mimetype + self.view = 'view_file.pt' + if mime and mime.startswith('text/'): + self.options.content = self.path.read() + else: + self.options.content = None + self.options.is_image = mime and mime.startswith('image/') + self.options.use_iframe = mime == 'text/html' |
