Initial work on UI and controller for file uploading:
* new toolbar button for attaching a file * button opens new Upload_pulldown() for uploading a file * began controller.Notebooks.upload_file() to process the upload
|
@ -63,4 +63,7 @@ settings = {
|
||||||
"""
|
"""
|
||||||
""",
|
""",
|
||||||
},
|
},
|
||||||
|
"/notebooks/upload_file": {
|
||||||
|
"stream_response": True
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,10 @@ def expose( view = None, rss = None ):
|
||||||
cherrypy.root.report_traceback()
|
cherrypy.root.report_traceback()
|
||||||
result = dict( error = u"An error occurred when processing your request. Please try again or contact support." )
|
result = dict( error = u"An error occurred when processing your request. Please try again or contact support." )
|
||||||
|
|
||||||
|
# if the result is a generator, it's streaming data, so just let CherryPy handle it
|
||||||
|
if hasattr( result, "gi_running" ):
|
||||||
|
return result
|
||||||
|
|
||||||
redirect = result.get( u"redirect", None )
|
redirect = result.get( u"redirect", None )
|
||||||
|
|
||||||
# try using the supplied view to render the result
|
# try using the supplied view to render the result
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import re
|
import re
|
||||||
|
import cgi
|
||||||
import cherrypy
|
import cherrypy
|
||||||
|
from cherrypy.filters import basefilter
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from Expose import expose
|
from Expose import expose
|
||||||
from Validate import validate, Valid_string, Validation_error, Valid_bool
|
from Validate import validate, Valid_string, Validation_error, Valid_bool
|
||||||
|
@ -15,6 +17,7 @@ from model.User_revision import User_revision
|
||||||
from view.Main_page import Main_page
|
from view.Main_page import Main_page
|
||||||
from view.Json import Json
|
from view.Json import Json
|
||||||
from view.Html_file import Html_file
|
from view.Html_file import Html_file
|
||||||
|
from view.Upload_page import Upload_page
|
||||||
|
|
||||||
|
|
||||||
class Access_error( Exception ):
|
class Access_error( Exception ):
|
||||||
|
@ -31,8 +34,35 @@ class Access_error( Exception ):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Upload_error( Exception ):
|
||||||
|
def __init__( self, message = None ):
|
||||||
|
if message is None:
|
||||||
|
message = u"An error occurred when uploading the file."
|
||||||
|
|
||||||
|
Exception.__init__( self, message )
|
||||||
|
self.__message = message
|
||||||
|
|
||||||
|
def to_dict( self ):
|
||||||
|
return dict(
|
||||||
|
error = self.__message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class File_upload_filter( basefilter.BaseFilter ):
|
||||||
|
def before_request_body( self ):
|
||||||
|
if cherrypy.request.path != "/notebooks/upload_file":
|
||||||
|
return
|
||||||
|
|
||||||
|
if cherrypy.request.method != "POST":
|
||||||
|
raise Upload_error()
|
||||||
|
|
||||||
|
# tell CherryPy not to parse the POST data itself for this URL
|
||||||
|
cherrypy.request.processRequestBody = False
|
||||||
|
|
||||||
|
|
||||||
class Notebooks( object ):
|
class Notebooks( object ):
|
||||||
WHITESPACE_PATTERN = re.compile( u"\s+" )
|
WHITESPACE_PATTERN = re.compile( u"\s+" )
|
||||||
|
_cpFilterList = [ File_upload_filter() ]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Controller for dealing with notebooks and their notes, corresponding to the "/notebooks" URL.
|
Controller for dealing with notebooks and their notes, corresponding to the "/notebooks" URL.
|
||||||
|
@ -1095,3 +1125,149 @@ class Notebooks( object ):
|
||||||
result[ "count" ] = count
|
result[ "count" ] = count
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@expose( view = Upload_page )
|
||||||
|
@validate(
|
||||||
|
notebook_id = Valid_id(),
|
||||||
|
note_id = Valid_id(),
|
||||||
|
)
|
||||||
|
def upload_page( self, notebook_id, note_id ):
|
||||||
|
"""
|
||||||
|
Provide the information necessary to display the file upload page.
|
||||||
|
|
||||||
|
@type notebook_id: unicode
|
||||||
|
@param notebook_id: id of the notebook that the upload will be to
|
||||||
|
@type note_id: unicode
|
||||||
|
@param note_id: id of the note that the upload will be to
|
||||||
|
@rtype: unicode
|
||||||
|
@return: rendered HTML page
|
||||||
|
"""
|
||||||
|
return dict(
|
||||||
|
notebook_id = notebook_id,
|
||||||
|
note_id = note_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
@expose()
|
||||||
|
@strongly_expire
|
||||||
|
@grab_user_id
|
||||||
|
@validate(
|
||||||
|
user_id = Valid_id( none_okay = True ),
|
||||||
|
)
|
||||||
|
def upload_file( self, user_id ):
|
||||||
|
"""
|
||||||
|
Upload a file from the client for attachment to a particular note.
|
||||||
|
|
||||||
|
@type notebook_id: unicode
|
||||||
|
@param notebook_id: id of the notebook that the upload is to
|
||||||
|
@type note_id: unicode
|
||||||
|
@param note_id: id of the note that the upload is to
|
||||||
|
@raise Access_error: the current user doesn't have access to the given notebook or note
|
||||||
|
@rtype: unicode
|
||||||
|
@return: rendered HTML page
|
||||||
|
"""
|
||||||
|
cherrypy.server.max_request_body_size = 0 # remove file size limit of 100 MB
|
||||||
|
cherrypy.response.timeout = 3600 # increase upload timeout to one hour (default is 5 min)
|
||||||
|
cherrypy.server.socket_timeout = 60 # increase socket timeout to one minute (default is 10 sec)
|
||||||
|
# TODO: increase to 8k
|
||||||
|
CHUNK_SIZE = 1#8 * 1024 # 8 Kb
|
||||||
|
|
||||||
|
headers = {}
|
||||||
|
for key, val in cherrypy.request.headers.iteritems():
|
||||||
|
headers[ key.lower() ] = val
|
||||||
|
|
||||||
|
try:
|
||||||
|
file_size = int( headers.get( "content-length", 0 ) )
|
||||||
|
except ValueError:
|
||||||
|
raise Upload_error()
|
||||||
|
if file_size <= 0:
|
||||||
|
raise Upload_error()
|
||||||
|
|
||||||
|
parsed_form = cgi.FieldStorage( fp = cherrypy.request.rfile, headers = headers, environ = { "REQUEST_METHOD": "POST" }, keep_blank_values = 1)
|
||||||
|
upload = parsed_form[ u"file" ]
|
||||||
|
notebook_id = parsed_form.getvalue( u"notebook_id" )
|
||||||
|
note_id = parsed_form.getvalue( u"note_id" )
|
||||||
|
filename = upload.filename.strip()
|
||||||
|
|
||||||
|
if not self.__users.check_access( user_id, notebook_id ):
|
||||||
|
raise Access_error()
|
||||||
|
|
||||||
|
def process_upload():
|
||||||
|
"""
|
||||||
|
Process the file upload while streaming a progress meter as it uploads.
|
||||||
|
"""
|
||||||
|
progress_bytes = 0
|
||||||
|
fraction_reported = 0.0
|
||||||
|
progress_width_em = 20
|
||||||
|
tick_increment = 0.01
|
||||||
|
progress_bar = u'<img src="/static/images/tick.png" style="width: %sem; height: 1em;" id="progress_bar" />' % \
|
||||||
|
( progress_width_em * tick_increment )
|
||||||
|
|
||||||
|
yield \
|
||||||
|
u"""
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link href="/static/css/upload.css" type="text/css" rel="stylesheet" />
|
||||||
|
<script type="text/javascript" src="/static/js/MochiKit.js"></script>
|
||||||
|
<meta content="text/html; charset=UTF-8" http_equiv="content-type" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not filename:
|
||||||
|
yield \
|
||||||
|
u"""
|
||||||
|
<div class="field_label">upload error: </div>
|
||||||
|
Please check that the filename is valid.
|
||||||
|
"""
|
||||||
|
return
|
||||||
|
|
||||||
|
base_filename = filename.split( u"/" )[ -1 ].split( u"\\" )[ -1 ]
|
||||||
|
yield \
|
||||||
|
u"""
|
||||||
|
<div class="field_label">uploading %s: </div>
|
||||||
|
<div id="progress_border">
|
||||||
|
%s
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
function tick( fraction ) {
|
||||||
|
setElementDimensions(
|
||||||
|
"progress_bar",
|
||||||
|
{ "w": %s * fraction }, "em"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
""" % ( cgi.escape( base_filename ), progress_bar, progress_width_em )
|
||||||
|
|
||||||
|
import time
|
||||||
|
while True:
|
||||||
|
chunk = upload.file.read( CHUNK_SIZE )
|
||||||
|
if not chunk: break
|
||||||
|
progress_bytes += len( chunk )
|
||||||
|
fraction_done = float( progress_bytes ) / float( file_size )
|
||||||
|
|
||||||
|
if fraction_done > fraction_reported + tick_increment:
|
||||||
|
yield '<script type="text/javascript">tick(%s)</script>;' % fraction_reported
|
||||||
|
fraction_reported += tick_increment
|
||||||
|
time.sleep(0.025) # TODO: removeme
|
||||||
|
|
||||||
|
# TODO: write to the database
|
||||||
|
|
||||||
|
if fraction_reported == 0:
|
||||||
|
yield "An error occurred when uploading the file."
|
||||||
|
return
|
||||||
|
|
||||||
|
# the file finished uploading, so fill out the progress meter to 100%
|
||||||
|
if fraction_reported < 1.0:
|
||||||
|
yield '<script type="text/javascript">tick(1.0)</script>;'
|
||||||
|
|
||||||
|
yield \
|
||||||
|
u"""
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
upload.file.close()
|
||||||
|
cherrypy.request.rfile.close()
|
||||||
|
|
||||||
|
return process_upload()
|
||||||
|
|
|
@ -10,6 +10,12 @@
|
||||||
background-image: url(/static/images/link_button.png);
|
background-image: url(/static/images/link_button.png);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#attach_button_preload {
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-image: url(/static/images/attach_button.png);
|
||||||
|
}
|
||||||
|
|
||||||
#bold_button_preload {
|
#bold_button_preload {
|
||||||
height: 0;
|
height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -71,6 +71,12 @@ img {
|
||||||
background-image: url(/static/images/link_button_hover.png);
|
background-image: url(/static/images/link_button_hover.png);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#attach_button_hover_preload {
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-image: url(/static/images/attach_button_hover.png);
|
||||||
|
}
|
||||||
|
|
||||||
#bold_button_hover_preload {
|
#bold_button_hover_preload {
|
||||||
height: 0;
|
height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -119,6 +125,12 @@ img {
|
||||||
background-image: url(/static/images/link_button_down_hover.png);
|
background-image: url(/static/images/link_button_down_hover.png);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#attach_button_down_hover_preload {
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-image: url(/static/images/attach_button_down_hover.png);
|
||||||
|
}
|
||||||
|
|
||||||
#bold_button_down_hover_preload {
|
#bold_button_down_hover_preload {
|
||||||
height: 0;
|
height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -167,6 +179,12 @@ img {
|
||||||
background-image: url(/static/images/link_button_down.png);
|
background-image: url(/static/images/link_button_down.png);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#attach_button_down_preload {
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-image: url(/static/images/attach_button_down.png);
|
||||||
|
}
|
||||||
|
|
||||||
#bold_button_down_preload {
|
#bold_button_down_preload {
|
||||||
height: 0;
|
height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -398,7 +416,7 @@ img {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
border: 1px solid #000000;
|
border: 1px solid #000000;
|
||||||
background: #ffff99;
|
background-color: #ffff99;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pulldown_link {
|
.pulldown_link {
|
||||||
|
@ -596,3 +614,10 @@ img {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #ff6600;
|
color: #ff6600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#upload_frame {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 40em;
|
||||||
|
height: 4em;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
html, body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 140%;
|
||||||
|
font-family: sans-serif;
|
||||||
|
background-color: #ffff99;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field_label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
border-style: outset;
|
||||||
|
border-width: 0px;
|
||||||
|
background-color: #d0e0f0;
|
||||||
|
font-size: 100%;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
background-color: #ffcc66;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress_border {
|
||||||
|
border: 1px solid #000000;
|
||||||
|
background-color: #ffffff;
|
||||||
|
width: 20em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tick_preload {
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-image: url(/static/images/tick.png);
|
||||||
|
}
|
|
@ -52,6 +52,7 @@ need to download or install anything if you just want to make a wiki.
|
||||||
|
|
||||||
<span id="new_note_button_preload"></span>
|
<span id="new_note_button_preload"></span>
|
||||||
<span id="link_button_preload"></span>
|
<span id="link_button_preload"></span>
|
||||||
|
<span id="attach_button_preload"></span>
|
||||||
<span id="bold_button_preload"></span>
|
<span id="bold_button_preload"></span>
|
||||||
<span id="italic_button_preload"></span>
|
<span id="italic_button_preload"></span>
|
||||||
<span id="underline_button_preload"></span>
|
<span id="underline_button_preload"></span>
|
||||||
|
|
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.0 KiB |
|
@ -2,7 +2,8 @@ Button dimensions are 40x40 pixels.
|
||||||
|
|
||||||
Button fonts are Bitstream Vera Sans (regular, bold, and mono oblique). Most
|
Button fonts are Bitstream Vera Sans (regular, bold, and mono oblique). Most
|
||||||
buttons are at 22 pt. The link button is at 12 pt. The list buttons are at 10
|
buttons are at 22 pt. The link button is at 12 pt. The list buttons are at 10
|
||||||
pt with a -4 pixel line spacing.
|
pt with a -4 pixel line spacing. Down (pressed) buttons have their text offset
|
||||||
|
two pixels down and two pixels to the right.
|
||||||
|
|
||||||
To make the white glowing effect (which isn't present on any buttons
|
To make the white glowing effect (which isn't present on any buttons
|
||||||
currently), start with black text on a transparent background in the Gimp.
|
currently), start with black text on a transparent background in the Gimp.
|
||||||
|
|
After Width: | Height: | Size: 743 B |
|
@ -0,0 +1,161 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://web.resource.org/cc/"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
id="svg79606"
|
||||||
|
sodipodi:version="0.32"
|
||||||
|
inkscape:version="0.44.1"
|
||||||
|
version="1.0"
|
||||||
|
sodipodi:docbase="/home/witten/luminotes/trunk/static/images"
|
||||||
|
sodipodi:docname="paperclip.svg">
|
||||||
|
<defs
|
||||||
|
id="defs79608">
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient5783">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#d3d7cf;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop5785" />
|
||||||
|
<stop
|
||||||
|
id="stop5791"
|
||||||
|
offset="0.5"
|
||||||
|
style="stop-color:#f5f5f5;stop-opacity:1;" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#bebebe;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop5787" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient3558">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop3560" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:0;"
|
||||||
|
offset="1"
|
||||||
|
id="stop3562" />
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient3558"
|
||||||
|
id="radialGradient3564"
|
||||||
|
cx="21.761711"
|
||||||
|
cy="23.07144"
|
||||||
|
fx="21.761711"
|
||||||
|
fy="23.07144"
|
||||||
|
r="15.571428"
|
||||||
|
gradientTransform="matrix(0.977282,3.554943e-8,-8.305337e-10,0.651376,-0.79443,15.82896)"
|
||||||
|
gradientUnits="userSpaceOnUse" />
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient5783"
|
||||||
|
id="linearGradient5789"
|
||||||
|
x1="23.505953"
|
||||||
|
y1="5.7753429"
|
||||||
|
x2="20.604948"
|
||||||
|
y2="29.85923"
|
||||||
|
gradientUnits="userSpaceOnUse" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="0.23529412"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="11.979167"
|
||||||
|
inkscape:cx="24"
|
||||||
|
inkscape:cy="24"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:grid-bbox="true"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:window-width="1400"
|
||||||
|
inkscape:window-height="1026"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
stroke="#d3d7cf"
|
||||||
|
inkscape:showpageshadow="false"
|
||||||
|
gridempspacing="4" />
|
||||||
|
<metadata
|
||||||
|
id="metadata79611">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title>Mail Attachment</dc:title>
|
||||||
|
<dc:date>2005-11-04</dc:date>
|
||||||
|
<dc:creator>
|
||||||
|
<cc:Agent>
|
||||||
|
<dc:title>Andreas Nilsson</dc:title>
|
||||||
|
</cc:Agent>
|
||||||
|
</dc:creator>
|
||||||
|
<dc:source>http://tango-project.org</dc:source>
|
||||||
|
<dc:subject>
|
||||||
|
<rdf:Bag>
|
||||||
|
<rdf:li>attachment</rdf:li>
|
||||||
|
<rdf:li>file</rdf:li>
|
||||||
|
</rdf:Bag>
|
||||||
|
</dc:subject>
|
||||||
|
<cc:license
|
||||||
|
rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
|
||||||
|
<dc:contributor>
|
||||||
|
<cc:Agent>
|
||||||
|
<dc:title>Garrett LeSage</dc:title>
|
||||||
|
</cc:Agent>
|
||||||
|
</dc:contributor>
|
||||||
|
</cc:Work>
|
||||||
|
<cc:License
|
||||||
|
rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
|
||||||
|
<cc:permits
|
||||||
|
rdf:resource="http://web.resource.org/cc/Reproduction" />
|
||||||
|
<cc:permits
|
||||||
|
rdf:resource="http://web.resource.org/cc/Distribution" />
|
||||||
|
<cc:requires
|
||||||
|
rdf:resource="http://web.resource.org/cc/Notice" />
|
||||||
|
<cc:requires
|
||||||
|
rdf:resource="http://web.resource.org/cc/Attribution" />
|
||||||
|
<cc:permits
|
||||||
|
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
|
||||||
|
<cc:requires
|
||||||
|
rdf:resource="http://web.resource.org/cc/ShareAlike" />
|
||||||
|
</cc:License>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer">
|
||||||
|
<g
|
||||||
|
id="g3902"
|
||||||
|
transform="matrix(0.862513,-0.506035,0.506035,0.862513,-8.845153,15.44454)">
|
||||||
|
<path
|
||||||
|
inkscape:r_cy="true"
|
||||||
|
inkscape:r_cx="true"
|
||||||
|
transform="translate(0.494048,1.056164)"
|
||||||
|
d="M 21.326337,9.3278633 L 10.449186,27.94227 C 8.5266861,31.23365 9.6775753,35.481172 13.008091,37.38221 L 15.102397,38.579075 C 18.434077,40.480111 22.732254,39.341738 24.655919,36.05036 L 36.41168,15.928621 C 38.335346,12.636117 37.625044,8.6405654 34.835356,7.0477444 C 32.045435,5.4549233 28.187846,6.8452672 26.265346,10.137772 L 18.109581,24.099704 C 16.186149,27.391081 15.978909,30.871442 17.647547,31.836583 C 19.317351,32.799475 22.257398,30.893938 24.179898,27.602558 L 28.142388,20.81957"
|
||||||
|
id="path7057"
|
||||||
|
style="fill:none;stroke:#888a85;stroke-width:3.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||||
|
<path
|
||||||
|
inkscape:r_cy="true"
|
||||||
|
inkscape:r_cx="true"
|
||||||
|
transform="translate(0.494048,1.168493)"
|
||||||
|
d="M 21.326337,9.2155349 L 10.449186,27.829941 C 8.5266861,31.121321 9.6775753,35.368843 13.008091,37.269881 L 15.102397,38.466746 C 18.434077,40.367782 22.732254,39.229409 24.655919,35.938031 L 36.41168,15.816292 C 38.335346,12.523788 37.625044,8.528237 34.835356,6.935416 C 32.045435,5.3425949 28.187846,6.7329388 26.265346,10.025444 L 18.109581,23.987375 C 16.186149,27.278752 15.978909,30.759113 17.647547,31.724254 C 19.317351,32.687146 22.257398,30.781609 24.179898,27.490229 L 28.142388,20.707241"
|
||||||
|
id="path7053"
|
||||||
|
style="fill:none;stroke:url(#linearGradient5789);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 150 B |
|
@ -492,6 +492,46 @@ Editor.prototype.end_link = function () {
|
||||||
return link;
|
return link;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Editor.prototype.insert_file_link = function ( filename, file_id ) {
|
||||||
|
// get the current selection, which is the link title
|
||||||
|
if ( this.iframe.contentWindow && this.iframe.contentWindow.getSelection ) { // browsers such as Firefox
|
||||||
|
var selection = this.iframe.contentWindow.getSelection();
|
||||||
|
|
||||||
|
// if no text is selected, then insert a link with the filename as the link title
|
||||||
|
if ( selection.toString().length == 0 ) {
|
||||||
|
this.insert_html( '<span id="placeholder_title">' + filename + '</span>' );
|
||||||
|
var placeholder = withDocument( this.document, function () { return getElement( "placeholder_title" ); } );
|
||||||
|
selection.selectAllChildren( placeholder );
|
||||||
|
|
||||||
|
this.exec_command( "createLink", "/files/" + file_id );
|
||||||
|
selection.collapseToEnd();
|
||||||
|
|
||||||
|
// replace the placeholder title span with just the filename, yielding an unselected link
|
||||||
|
var link = placeholder.parentNode;
|
||||||
|
link.innerHTML = filename;
|
||||||
|
link.target = "_new";
|
||||||
|
// otherwise, just create a link with the selected text as the link title
|
||||||
|
} else {
|
||||||
|
this.exec_command( "createLink", "/files/" + file_id );
|
||||||
|
var link = this.find_link_at_cursor();
|
||||||
|
link.target = "_new";
|
||||||
|
}
|
||||||
|
} else if ( this.document.selection ) { // browsers such as IE
|
||||||
|
var range = this.document.selection.createRange();
|
||||||
|
|
||||||
|
// if no text is selected, then insert a link with the filename as the link title
|
||||||
|
if ( range.text.length == 0 ) {
|
||||||
|
range.text = filename;
|
||||||
|
range.moveStart( "character", -1 * filename.length );
|
||||||
|
range.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.exec_command( "createLink", "/files/" + file_id );
|
||||||
|
var link = this.find_link_at_cursor();
|
||||||
|
link.target = "_new";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Editor.prototype.find_link_at_cursor = function () {
|
Editor.prototype.find_link_at_cursor = function () {
|
||||||
if ( this.iframe.contentWindow && this.iframe.contentWindow.getSelection ) { // browsers such as Firefox
|
if ( this.iframe.contentWindow && this.iframe.contentWindow.getSelection ) { // browsers such as Firefox
|
||||||
var selection = this.iframe.contentWindow.getSelection();
|
var selection = this.iframe.contentWindow.getSelection();
|
||||||
|
@ -542,6 +582,18 @@ Editor.prototype.find_link_at_cursor = function () {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Editor.prototype.node_at_cursor = function () {
|
||||||
|
if ( this.iframe.contentWindow && this.iframe.contentWindow.getSelection ) { // browsers such as Firefox
|
||||||
|
var selection = this.iframe.contentWindow.getSelection();
|
||||||
|
return selection.anchorNode;
|
||||||
|
} else if ( this.document.selection ) { // browsers such as IE
|
||||||
|
var range = this.document.selection.createRange();
|
||||||
|
return range.parentElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Editor.prototype.focus = function () {
|
Editor.prototype.focus = function () {
|
||||||
if ( /Opera/.test( navigator.userAgent ) )
|
if ( /Opera/.test( navigator.userAgent ) )
|
||||||
this.iframe.focus();
|
this.iframe.focus();
|
||||||
|
|
|
@ -253,6 +253,7 @@ Wiki.prototype.populate = function ( startup_notes, current_notes, note_read_wri
|
||||||
connect( window, "onunload", function ( event ) { self.editor_focused( null, true ); } );
|
connect( window, "onunload", function ( event ) { self.editor_focused( null, true ); } );
|
||||||
connect( "newNote", "onclick", this, "create_blank_editor" );
|
connect( "newNote", "onclick", this, "create_blank_editor" );
|
||||||
connect( "createLink", "onclick", this, "toggle_link_button" );
|
connect( "createLink", "onclick", this, "toggle_link_button" );
|
||||||
|
connect( "attachFile", "onclick", this, "toggle_attach_button" );
|
||||||
connect( "bold", "onclick", function ( event ) { self.toggle_button( event, "bold" ); } );
|
connect( "bold", "onclick", function ( event ) { self.toggle_button( event, "bold" ); } );
|
||||||
connect( "italic", "onclick", function ( event ) { self.toggle_button( event, "italic" ); } );
|
connect( "italic", "onclick", function ( event ) { self.toggle_button( event, "italic" ); } );
|
||||||
connect( "underline", "onclick", function ( event ) { self.toggle_button( event, "underline" ); } );
|
connect( "underline", "onclick", function ( event ) { self.toggle_button( event, "underline" ); } );
|
||||||
|
@ -262,6 +263,7 @@ Wiki.prototype.populate = function ( startup_notes, current_notes, note_read_wri
|
||||||
|
|
||||||
this.make_image_button( "newNote", "new_note", true );
|
this.make_image_button( "newNote", "new_note", true );
|
||||||
this.make_image_button( "createLink", "link" );
|
this.make_image_button( "createLink", "link" );
|
||||||
|
this.make_image_button( "attachFile", "attach" );
|
||||||
this.make_image_button( "bold" );
|
this.make_image_button( "bold" );
|
||||||
this.make_image_button( "italic" );
|
this.make_image_button( "italic" );
|
||||||
this.make_image_button( "underline" );
|
this.make_image_button( "underline" );
|
||||||
|
@ -958,6 +960,27 @@ Wiki.prototype.toggle_link_button = function ( event ) {
|
||||||
event.stop();
|
event.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Wiki.prototype.toggle_attach_button = function ( event ) {
|
||||||
|
if ( this.focused_editor && this.focused_editor.read_write ) {
|
||||||
|
this.focused_editor.focus();
|
||||||
|
|
||||||
|
// if the pulldown is already open, then just close it
|
||||||
|
var pulldown_id = "upload_" + this.focused_editor.id;
|
||||||
|
var existing_div = getElement( pulldown_id );
|
||||||
|
if ( existing_div ) {
|
||||||
|
existing_div.pulldown.shutdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clear_messages();
|
||||||
|
this.clear_pulldowns();
|
||||||
|
|
||||||
|
new Upload_pulldown( this, this.notebook_id, this.invoker, this.focused_editor, this.focused_editor.node_at_cursor() );
|
||||||
|
}
|
||||||
|
|
||||||
|
event.stop();
|
||||||
|
}
|
||||||
|
|
||||||
Wiki.prototype.hide_editor = function ( event, editor ) {
|
Wiki.prototype.hide_editor = function ( event, editor ) {
|
||||||
this.clear_messages();
|
this.clear_messages();
|
||||||
this.clear_pulldowns();
|
this.clear_pulldowns();
|
||||||
|
@ -2186,3 +2209,58 @@ Link_pulldown.prototype.shutdown = function () {
|
||||||
if ( this.link )
|
if ( this.link )
|
||||||
this.link.pulldown = null;
|
this.link.pulldown = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Upload_pulldown( wiki, notebook_id, invoker, editor, anchor ) {
|
||||||
|
this.anchor = anchor;
|
||||||
|
|
||||||
|
Pulldown.call( this, wiki, notebook_id, "upload_" + editor.id, anchor, editor.iframe );
|
||||||
|
wiki.down_image_button( "attachFile" );
|
||||||
|
|
||||||
|
this.invoker = invoker;
|
||||||
|
this.editor = editor;
|
||||||
|
this.iframe = createDOM( "iframe", {
|
||||||
|
"src": "/notebooks/upload_page?notebook_id=" + notebook_id + "¬e_id=" + editor.id,
|
||||||
|
"frameBorder": "0",
|
||||||
|
"scrolling": "no",
|
||||||
|
"id": "upload_frame",
|
||||||
|
"name": "upload_frame"
|
||||||
|
} );
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
connect( this.iframe, "onload", function ( event ) { self.init_frame(); } );
|
||||||
|
|
||||||
|
appendChildNodes( this.div, this.iframe );
|
||||||
|
}
|
||||||
|
|
||||||
|
Upload_pulldown.prototype = new function () { this.prototype = Pulldown.prototype; };
|
||||||
|
Upload_pulldown.prototype.constructor = Upload_pulldown;
|
||||||
|
|
||||||
|
Upload_pulldown.prototype.init_frame = function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
withDocument( this.iframe.contentDocument, function () {
|
||||||
|
connect( "upload_button", "onclick", function ( event ) {
|
||||||
|
withDocument( self.iframe.contentDocument, function () {
|
||||||
|
self.upload_started( getElement( "file" ).value );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
Upload_pulldown.prototype.upload_started = function ( filename ) {
|
||||||
|
// get the basename of the file
|
||||||
|
var pieces = filename.split( "/" );
|
||||||
|
filename = pieces[ pieces.length - 1 ];
|
||||||
|
pieces = filename.split( "\\" );
|
||||||
|
filename = pieces[ pieces.length - 1 ];
|
||||||
|
|
||||||
|
this.editor.insert_file_link( filename );
|
||||||
|
}
|
||||||
|
|
||||||
|
Upload_pulldown.prototype.shutdown = function () {
|
||||||
|
Pulldown.prototype.shutdown.call( this );
|
||||||
|
this.wiki.up_image_button( "attachFile" );
|
||||||
|
|
||||||
|
disconnectAll( this.file_input );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,10 +86,6 @@ class Link_area( Div ):
|
||||||
id = u"share_notebook_link",
|
id = u"share_notebook_link",
|
||||||
title = u"Share this notebook with others.",
|
title = u"Share this notebook with others.",
|
||||||
),
|
),
|
||||||
Span(
|
|
||||||
u"new!",
|
|
||||||
class_ = u"new_feature_text",
|
|
||||||
),
|
|
||||||
class_ = u"link_area_item",
|
class_ = u"link_area_item",
|
||||||
) or None,
|
) or None,
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,13 @@ class Toolbar( Div ):
|
||||||
width = u"40", height = u"40",
|
width = u"40", height = u"40",
|
||||||
class_ = "image_button",
|
class_ = "image_button",
|
||||||
) ),
|
) ),
|
||||||
|
Div( Input(
|
||||||
|
type = u"image",
|
||||||
|
id = u"attachFile", title = u"attach file",
|
||||||
|
src = u"/static/images/attach_button.png",
|
||||||
|
width = u"40", height = u"40",
|
||||||
|
class_ = "image_button",
|
||||||
|
) ),
|
||||||
),
|
),
|
||||||
P(
|
P(
|
||||||
Div( Input(
|
Div( Input(
|
||||||
|
@ -73,6 +80,7 @@ class Toolbar( Div ):
|
||||||
|
|
||||||
Span( id = "new_note_button_hover_preload" ),
|
Span( id = "new_note_button_hover_preload" ),
|
||||||
Span( id = "link_button_hover_preload" ),
|
Span( id = "link_button_hover_preload" ),
|
||||||
|
Span( id = "attach_button_hover_preload" ),
|
||||||
Span( id = "bold_button_hover_preload" ),
|
Span( id = "bold_button_hover_preload" ),
|
||||||
Span( id = "italic_button_hover_preload" ),
|
Span( id = "italic_button_hover_preload" ),
|
||||||
Span( id = "underline_button_hover_preload" ),
|
Span( id = "underline_button_hover_preload" ),
|
||||||
|
@ -82,6 +90,7 @@ class Toolbar( Div ):
|
||||||
|
|
||||||
Span( id = "new_note_button_down_hover_preload" ),
|
Span( id = "new_note_button_down_hover_preload" ),
|
||||||
Span( id = "link_button_down_hover_preload" ),
|
Span( id = "link_button_down_hover_preload" ),
|
||||||
|
Span( id = "attach_button_down_hover_preload" ),
|
||||||
Span( id = "bold_button_down_hover_preload" ),
|
Span( id = "bold_button_down_hover_preload" ),
|
||||||
Span( id = "italic_button_down_hover_preload" ),
|
Span( id = "italic_button_down_hover_preload" ),
|
||||||
Span( id = "underline_button_down_hover_preload" ),
|
Span( id = "underline_button_down_hover_preload" ),
|
||||||
|
@ -91,6 +100,7 @@ class Toolbar( Div ):
|
||||||
|
|
||||||
Span( id = "new_note_button_down_preload" ),
|
Span( id = "new_note_button_down_preload" ),
|
||||||
Span( id = "link_button_down_preload" ),
|
Span( id = "link_button_down_preload" ),
|
||||||
|
Span( id = "attach_button_down_preload" ),
|
||||||
Span( id = "bold_button_down_preload" ),
|
Span( id = "bold_button_down_preload" ),
|
||||||
Span( id = "italic_button_down_preload" ),
|
Span( id = "italic_button_down_preload" ),
|
||||||
Span( id = "underline_button_down_preload" ),
|
Span( id = "underline_button_down_preload" ),
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
from Tags import Html, Head, Link, Meta, Body, P, Form, Span, Input
|
||||||
|
|
||||||
|
|
||||||
|
class Upload_page( Html ):
|
||||||
|
def __init__( self, notebook_id, note_id ):
|
||||||
|
Html.__init__(
|
||||||
|
self,
|
||||||
|
Head(
|
||||||
|
Link( href = u"/static/css/upload.css", type = u"text/css", rel = u"stylesheet" ),
|
||||||
|
Meta( content = u"text/html; charset=UTF-8", http_equiv = u"content-type" ),
|
||||||
|
),
|
||||||
|
Body(
|
||||||
|
Form(
|
||||||
|
Span( u"attach file: ", class_ = u"field_label" ),
|
||||||
|
Input( type = u"file", id = u"file", name = u"file", size = u"30" ),
|
||||||
|
Input( type = u"submit", id = u"upload_button", class_ = u"button", value = u"upload" ),
|
||||||
|
Input( type = u"hidden", id = u"notebook_id", name = u"notebook_id", value = notebook_id ),
|
||||||
|
Input( type = u"hidden", id = u"note_id", name = u"note_id", value = note_id ),
|
||||||
|
action = u"/notebooks/upload_file",
|
||||||
|
method = u"post",
|
||||||
|
enctype = u"multipart/form-data",
|
||||||
|
),
|
||||||
|
P( u"Please select a file to upload." ),
|
||||||
|
Span( id = u"tick_preload" ),
|
||||||
|
),
|
||||||
|
)
|