Factored out file upload methods from Notebooks to new Files controller.
Changed file link insertion code to reuse existing link creation code.
This commit is contained in:
parent
0cf2b5bda7
commit
e56503903b
|
@ -0,0 +1,224 @@
|
||||||
|
import cgi
|
||||||
|
import cherrypy
|
||||||
|
from cherrypy.filters import basefilter
|
||||||
|
from Expose import expose
|
||||||
|
from Validate import validate
|
||||||
|
from Database import Valid_id
|
||||||
|
from Users import grab_user_id
|
||||||
|
from Expire import strongly_expire
|
||||||
|
from view.Upload_page import Upload_page
|
||||||
|
|
||||||
|
|
||||||
|
class Access_error( Exception ):
|
||||||
|
def __init__( self, message = None ):
|
||||||
|
if message is None:
|
||||||
|
message = u"Sorry, you don't have access to do that. Please make sure you're logged in as the correct user."
|
||||||
|
|
||||||
|
Exception.__init__( self, message )
|
||||||
|
self.__message = message
|
||||||
|
|
||||||
|
def to_dict( self ):
|
||||||
|
return dict(
|
||||||
|
error = self.__message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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 != "/files/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 Files( object ):
|
||||||
|
_cpFilterList = [ File_upload_filter() ]
|
||||||
|
|
||||||
|
"""
|
||||||
|
Controller for dealing with uploaded files, corresponding to the "/files" URL.
|
||||||
|
"""
|
||||||
|
def __init__( self, database, users ):
|
||||||
|
"""
|
||||||
|
Create a new Files object.
|
||||||
|
|
||||||
|
@type database: controller.Database
|
||||||
|
@param database: database that files are stored in
|
||||||
|
@type users: controller.Users
|
||||||
|
@param users: controller for all users
|
||||||
|
@rtype: Files
|
||||||
|
@return: newly constructed Files
|
||||||
|
"""
|
||||||
|
self.__database = database
|
||||||
|
self.__users = users
|
||||||
|
|
||||||
|
@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>
|
||||||
|
<table><tr>
|
||||||
|
<td><div id="progress_border">
|
||||||
|
%s
|
||||||
|
</div></td>
|
||||||
|
<td></td>
|
||||||
|
<td><span id="status"></span></td>
|
||||||
|
</tr></table>
|
||||||
|
<script type="text/javascript">
|
||||||
|
function tick( fraction ) {
|
||||||
|
setElementDimensions(
|
||||||
|
"progress_bar",
|
||||||
|
{ "w": %s * fraction }, "em"
|
||||||
|
);
|
||||||
|
if ( fraction == 1.0 )
|
||||||
|
replaceChildNodes( "status", "100%%" );
|
||||||
|
else
|
||||||
|
replaceChildNodes( "status", Math.floor( fraction * 100.0 ) + "%%" );
|
||||||
|
}
|
||||||
|
</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()
|
|
@ -1,7 +1,5 @@
|
||||||
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
|
||||||
|
@ -17,7 +15,6 @@ 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 ):
|
||||||
|
@ -34,36 +31,8 @@ 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.
|
||||||
"""
|
"""
|
||||||
|
@ -1125,149 +1094,3 @@ 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()
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ from Expire import strongly_expire
|
||||||
from Validate import validate, Valid_int, Valid_string
|
from Validate import validate, Valid_int, Valid_string
|
||||||
from Notebooks import Notebooks
|
from Notebooks import Notebooks
|
||||||
from Users import Users, grab_user_id
|
from Users import Users, grab_user_id
|
||||||
|
from Files import Files
|
||||||
from Database import Valid_id
|
from Database import Valid_id
|
||||||
from model.Note import Note
|
from model.Note import Note
|
||||||
from model.Notebook import Notebook
|
from model.Notebook import Notebook
|
||||||
|
@ -43,6 +44,7 @@ class Root( object ):
|
||||||
settings[ u"global" ].get( u"luminotes.rate_plans", [] ),
|
settings[ u"global" ].get( u"luminotes.rate_plans", [] ),
|
||||||
)
|
)
|
||||||
self.__notebooks = Notebooks( database, self.__users )
|
self.__notebooks = Notebooks( database, self.__users )
|
||||||
|
self.__files = Files( database, self.__users )
|
||||||
|
|
||||||
@expose( Main_page )
|
@expose( Main_page )
|
||||||
@grab_user_id
|
@grab_user_id
|
||||||
|
@ -353,3 +355,4 @@ class Root( object ):
|
||||||
database = property( lambda self: self.__database )
|
database = property( lambda self: self.__database )
|
||||||
notebooks = property( lambda self: self.__notebooks )
|
notebooks = property( lambda self: self.__notebooks )
|
||||||
users = property( lambda self: self.__users )
|
users = property( lambda self: self.__users )
|
||||||
|
files = property( lambda self: self.__files )
|
||||||
|
|
|
@ -39,6 +39,10 @@ div {
|
||||||
height: 1em;
|
height: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
#tick_preload {
|
#tick_preload {
|
||||||
height: 0;
|
height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -416,7 +416,7 @@ Editor.prototype.empty = function () {
|
||||||
return ( scrapeText( this.document.body ).length == 0 );
|
return ( scrapeText( this.document.body ).length == 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
Editor.prototype.start_link = function () {
|
Editor.prototype.insert_link = function ( url ) {
|
||||||
// get the current selection, which is the link title
|
// get the current selection, which is the link title
|
||||||
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();
|
||||||
|
@ -428,7 +428,7 @@ Editor.prototype.start_link = function () {
|
||||||
var placeholder = withDocument( this.document, function () { return getElement( "placeholder_title" ); } );
|
var placeholder = withDocument( this.document, function () { return getElement( "placeholder_title" ); } );
|
||||||
selection.selectAllChildren( placeholder );
|
selection.selectAllChildren( placeholder );
|
||||||
|
|
||||||
this.exec_command( "createLink", "/notebooks/" + this.notebook_id + "?note_id=new" );
|
this.exec_command( "createLink", url );
|
||||||
selection.collapseToEnd();
|
selection.collapseToEnd();
|
||||||
|
|
||||||
// hack to prevent Firefox from erasing spaces before links that happen to be at the end of list items
|
// hack to prevent Firefox from erasing spaces before links that happen to be at the end of list items
|
||||||
|
@ -443,7 +443,7 @@ Editor.prototype.start_link = function () {
|
||||||
// otherwise, just create a link with the selected text as the link title
|
// otherwise, just create a link with the selected text as the link title
|
||||||
} else {
|
} else {
|
||||||
this.link_started = null;
|
this.link_started = null;
|
||||||
this.exec_command( "createLink", "/notebooks/" + this.notebook_id + "?note_id=new" );
|
this.exec_command( "createLink", url );
|
||||||
return this.find_link_at_cursor();
|
return this.find_link_at_cursor();
|
||||||
}
|
}
|
||||||
} else if ( this.document.selection ) { // browsers such as IE
|
} else if ( this.document.selection ) { // browsers such as IE
|
||||||
|
@ -455,16 +455,24 @@ Editor.prototype.start_link = function () {
|
||||||
range.text = " ";
|
range.text = " ";
|
||||||
range.moveStart( "character", -1 );
|
range.moveStart( "character", -1 );
|
||||||
range.select();
|
range.select();
|
||||||
this.exec_command( "createLink", "/notebooks/" + this.notebook_id + "?note_id=new" );
|
this.exec_command( "createLink", url );
|
||||||
this.link_started = this.find_link_at_cursor();
|
this.link_started = this.find_link_at_cursor();
|
||||||
} else {
|
} else {
|
||||||
this.link_started = null;
|
this.link_started = null;
|
||||||
this.exec_command( "createLink", "/notebooks/" + this.notebook_id + "?note_id=new" );
|
this.exec_command( "createLink", url );
|
||||||
return this.find_link_at_cursor();
|
return this.find_link_at_cursor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Editor.prototype.start_link = function () {
|
||||||
|
return this.insert_link( "/notebooks/" + this.notebook_id + "?note_id=new" );
|
||||||
|
}
|
||||||
|
|
||||||
|
Editor.prototype.start_file_link = function () {
|
||||||
|
return this.insert_link( "/files/new" );
|
||||||
|
}
|
||||||
|
|
||||||
Editor.prototype.end_link = function () {
|
Editor.prototype.end_link = function () {
|
||||||
this.link_started = null;
|
this.link_started = null;
|
||||||
var link = this.find_link_at_cursor();
|
var link = this.find_link_at_cursor();
|
||||||
|
@ -492,46 +500,6 @@ 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();
|
||||||
|
@ -582,18 +550,6 @@ 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();
|
||||||
|
|
|
@ -932,7 +932,21 @@ Wiki.prototype.update_toolbar = function() {
|
||||||
this.update_button( "title", "h3", node_names );
|
this.update_button( "title", "h3", node_names );
|
||||||
this.update_button( "insertUnorderedList", "ul", node_names );
|
this.update_button( "insertUnorderedList", "ul", node_names );
|
||||||
this.update_button( "insertOrderedList", "ol", node_names );
|
this.update_button( "insertOrderedList", "ol", node_names );
|
||||||
this.update_button( "createLink", "a", node_names );
|
|
||||||
|
var link = this.focused_editor.find_link_at_cursor();
|
||||||
|
if ( link ) {
|
||||||
|
// determine whether the link is a note link or a file link
|
||||||
|
if ( link.target || !/\/files\//.test( link.href ) ) {
|
||||||
|
this.down_image_button( "createLink" );
|
||||||
|
this.up_image_button( "attachFile" );
|
||||||
|
} else {
|
||||||
|
this.up_image_button( "createLink" );
|
||||||
|
this.down_image_button( "attachFile" );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.up_image_button( "createLink" );
|
||||||
|
this.up_image_button( "attachFile" );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Wiki.prototype.toggle_link_button = function ( event ) {
|
Wiki.prototype.toggle_link_button = function ( event ) {
|
||||||
|
@ -975,7 +989,7 @@ Wiki.prototype.toggle_attach_button = function ( event ) {
|
||||||
this.clear_messages();
|
this.clear_messages();
|
||||||
this.clear_pulldowns();
|
this.clear_pulldowns();
|
||||||
|
|
||||||
new Upload_pulldown( this, this.notebook_id, this.invoker, this.focused_editor, this.focused_editor.node_at_cursor() );
|
new Upload_pulldown( this, this.notebook_id, this.invoker, this.focused_editor );
|
||||||
}
|
}
|
||||||
|
|
||||||
event.stop();
|
event.stop();
|
||||||
|
@ -2210,16 +2224,17 @@ Link_pulldown.prototype.shutdown = function () {
|
||||||
this.link.pulldown = null;
|
this.link.pulldown = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Upload_pulldown( wiki, notebook_id, invoker, editor, anchor ) {
|
function Upload_pulldown( wiki, notebook_id, invoker, editor ) {
|
||||||
this.anchor = anchor;
|
editor.start_file_link();
|
||||||
|
this.link = editor.find_link_at_cursor();
|
||||||
|
|
||||||
Pulldown.call( this, wiki, notebook_id, "upload_" + editor.id, anchor, editor.iframe );
|
Pulldown.call( this, wiki, notebook_id, "upload_" + editor.id, this.link, editor.iframe );
|
||||||
wiki.down_image_button( "attachFile" );
|
wiki.down_image_button( "attachFile" );
|
||||||
|
|
||||||
this.invoker = invoker;
|
this.invoker = invoker;
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
this.iframe = createDOM( "iframe", {
|
this.iframe = createDOM( "iframe", {
|
||||||
"src": "/notebooks/upload_page?notebook_id=" + notebook_id + "¬e_id=" + editor.id,
|
"src": "/files/upload_page?notebook_id=" + notebook_id + "¬e_id=" + editor.id,
|
||||||
"frameBorder": "0",
|
"frameBorder": "0",
|
||||||
"scrolling": "no",
|
"scrolling": "no",
|
||||||
"id": "upload_frame",
|
"id": "upload_frame",
|
||||||
|
@ -2237,10 +2252,11 @@ Upload_pulldown.prototype.constructor = Upload_pulldown;
|
||||||
|
|
||||||
Upload_pulldown.prototype.init_frame = function () {
|
Upload_pulldown.prototype.init_frame = function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var doc = this.iframe.contentDocument || this.iframe.contentWindow.document;
|
||||||
|
|
||||||
withDocument( this.iframe.contentDocument, function () {
|
withDocument( doc, function () {
|
||||||
connect( "upload_button", "onclick", function ( event ) {
|
connect( "upload_button", "onclick", function ( event ) {
|
||||||
withDocument( self.iframe.contentDocument, function () {
|
withDocument( doc, function () {
|
||||||
self.upload_started( getElement( "file" ).value );
|
self.upload_started( getElement( "file" ).value );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
@ -2254,7 +2270,10 @@ Upload_pulldown.prototype.upload_started = function ( filename ) {
|
||||||
pieces = filename.split( "\\" );
|
pieces = filename.split( "\\" );
|
||||||
filename = pieces[ pieces.length - 1 ];
|
filename = pieces[ pieces.length - 1 ];
|
||||||
|
|
||||||
this.editor.insert_file_link( filename );
|
// the current title is blank, replace the title with the upload's filename
|
||||||
|
if ( link_title( this.link ) == "" )
|
||||||
|
replaceChildNodes( this.link, this.editor.document.createTextNode( filename ) );
|
||||||
|
// TODO: set the link's href to the file
|
||||||
}
|
}
|
||||||
|
|
||||||
Upload_pulldown.prototype.shutdown = function () {
|
Upload_pulldown.prototype.shutdown = function () {
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Upload_page( Html ):
|
||||||
Input( type = u"submit", id = u"upload_button", class_ = u"button", value = u"upload" ),
|
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"notebook_id", name = u"notebook_id", value = notebook_id ),
|
||||||
Input( type = u"hidden", id = u"note_id", name = u"note_id", value = note_id ),
|
Input( type = u"hidden", id = u"note_id", name = u"note_id", value = note_id ),
|
||||||
action = u"/notebooks/upload_file",
|
action = u"/files/upload_file",
|
||||||
method = u"post",
|
method = u"post",
|
||||||
enctype = u"multipart/form-data",
|
enctype = u"multipart/form-data",
|
||||||
),
|
),
|
||||||
|
|
Reference in New Issue