Use require.js

This commit is contained in:
benweet 2013-04-02 19:42:47 +01:00
parent f32a1e41a6
commit 4f7cba9738
22 changed files with 3171 additions and 832 deletions

12
LICENSE.txt Normal file
View File

@ -0,0 +1,12 @@
StackEdit - The Markdown editor powered by PageDown.
Copyright 2013 Benoit Schweblin (http://www.benoitschweblin.com)
Licensed under an Apache License (http://www.apache.org/licenses/LICENSE-2.0)
Includes:
jQuery - http://jquery.com/
Bootstrap - http://twitter.github.com/bootstrap/
RequireJS - http://requirejs.org/
PageDown - https://code.google.com/p/pagedown/
UI Layout - http://layout.jquery-dev.net/
jGrowl - https://github.com/stanlemon/jGrowl/

View File

@ -1,4 +1,22 @@
stackedit stackedit
========= =========
The Markdown editor powered by PageDown StackEdit is a free, open-source Markdown editor based on PageDown, the Markdown library used by Stack Overflow and the other Stack Exchange sites.
### StackEdit can:
- Manage multiple Markdown documents locally
- Export your documents in Markdown or HTML format
- Synchronize your Markdown documents in the Cloud
- Edit existing Markdown documents from Google Drive
### Features:
- Real-time HTML preview
- WYSIWYG control buttons
- Configurable layout
- Offline editing
### File synchronization:
- Google Drive

View File

@ -1 +1 @@
CACHE MANIFEST # v5 CACHE: index.html css/bootstrap.css css/jgrowl.css css/main.css js/async-runner.js js/base64.js js/bootstrap.js js/gdrive.js js/jquery.jgrowl.js js/jquery.js js/jquery.layout.js js/jquery-ui.custom.js js/main.js js/Markdown.Converter.js js/Markdown.Editor.js async-runner.js js/base64.js .js js/synchronizer.js img/ajax-loader.gif img/dropbox.png img/gdrive.png img/glyphicons-halflings.png img/glyphicons-halflings-white.png img/stackedit-16.png img/stackedit-32.ico NETWORK: * CACHE MANIFEST # v5 CACHE: index.html css/bootstrap.css css/jgrowl.css css/main.css js/async-runner.js js/bootstrap.js js/async-runner.js js/bootstrap.js js/config.js js/gdrive.js js/jgrowl.js js/jquery.js async-runner.js js/jquery.layout.js async-runner.js js/jquery-ui.custom.js js/main.js js/Markdown.Converter.js js/Markdown.Editor.js async-runner.js js/main.js js/base64.js .js js/synchronizer.js img/ajax-loader.gif img/dropbox.png img/gdrive.png img/glyphicons-halflings.png img/glyphicons-halflings-white.png img/stackedit-16.png img/stackedit-32.ico NETWORK: *

View File

@ -1,3 +1,6 @@
@import url("bootstrap.css");
@import url("jgrowl.css");
body { body {
background-color: #f5f5f5; background-color: #f5f5f5;
} }
@ -22,10 +25,14 @@ div, span, a, ul, li, textarea, input, button {
text-shadow: none !important; text-shadow: none !important;
} }
.btn, .navbar-inner, .ui-layout-east, .ui-layout-south, textarea, input { .btn, .navbar-inner, .ui-layout-east, .ui-layout-south, textarea, input, .input-append .add-on {
border: none !important; border: none !important;
} }
.border, .dropdown-menu {
border: 1px solid #ddd !important;
}
.navbar-inner .btn { .navbar-inner .btn {
background-color: #ddd; background-color: #ddd;
} }
@ -61,7 +68,7 @@ div, span, a, ul, li, textarea, input, button {
background-color: #777; background-color: #777;
} }
input[disabled], select[disabled], textarea[disabled], input[readonly], select[readonly], textarea[readonly] { input[disabled], select[disabled], textarea[disabled], input[readonly], select[readonly], textarea[readonly], .input-append .add-on {
cursor: not-allowed; cursor: not-allowed;
background-color: #f5f5f5; background-color: #f5f5f5;
} }
@ -119,10 +126,6 @@ hr {
margin: 4px 5px 0; margin: 4px 5px 0;
} }
.dropdown-menu {
border-color: #ddd
}
.dropdown-menu i { .dropdown-menu i {
margin-right: 5px; margin-right: 5px;
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

125
img/stackedit-promo.svg Normal file
View File

@ -0,0 +1,125 @@
<?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://creativecommons.org/ns#"
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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="800"
height="200"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="stackedit-promo.svg"
inkscape:export-filename="C:\Documents and Settings\g550003\Mes documents\Mes images\stackedit-promo.png"
inkscape:export-xdpi="103.5"
inkscape:export-ydpi="103.5">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="521.42724"
inkscape:cy="-97.239171"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1143"
inkscape:window-x="-4"
inkscape:window-y="-4"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid3000"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<metadata
id="metadata7">
<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></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-852.36217)">
<text
xml:space="preserve"
style="font-size:144px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Corbel;-inkscape-font-specification:Corbel"
x="221.88937"
y="1017.7139"
id="text2991"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan2993"
x="221.88937"
y="1017.7139">stack<tspan
style="font-size:144px;font-weight:bold;-inkscape-font-specification:Corbel Bold"
id="tspan2995">edit</tspan></tspan></text>
<g
id="g3013"
transform="matrix(1.015625,0,0,1.015625,-0.48727889,-13.775147)">
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path3001"
d="m 41.18585,941.60943 0,69.99997 140,0 0,-69.99997"
style="fill:none;stroke:#626265;stroke-width:20;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path3789"
d="m 61.185851,986.60941 89.999999,0"
style="fill:none;stroke:#6c6c70;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path3791"
d="m 61.185851,966.60944 79.999999,0"
style="fill:none;stroke:#95785b;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path3793"
d="m 61.185851,946.60937 39.999999,0"
style="fill:none;stroke:#bc8e48;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path3795"
d="m 61.185851,926.6094 99.999999,0"
style="fill:none;stroke:#d38b28;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path3797"
d="m 61.185851,906.60943 69.999999,0"
style="fill:none;stroke:#fd8a07;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3799"
d="m 61.185851,886.60945 99.999999,0"
style="fill:none;stroke:#fe7a15;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

115
img/stackedit.svg Normal file
View File

@ -0,0 +1,115 @@
<?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://creativecommons.org/ns#"
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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32"
height="32"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="stackedit-dark.svg"
inkscape:export-filename="C:\Documents and Settings\g550003\Mes documents\Mes images\stackedit-128.png"
inkscape:export-xdpi="360"
inkscape:export-ydpi="360">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.4721116"
inkscape:cx="89.111669"
inkscape:cy="-23.177627"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1143"
inkscape:window-x="-4"
inkscape:window-y="-4"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid3000"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<metadata
id="metadata7">
<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></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1020.3622)">
<path
style="fill:none;stroke:#626265;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 2,-2 0,14 28,0 0,-14"
id="path3001"
inkscape:connector-curvature="0"
transform="translate(0,1036.3622)"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#6c6c70;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 6,23 18,0"
id="path3789"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#95785b;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 6,19 16,0"
id="path3791"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#bc8e48;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 6,15 8,0"
id="path3793"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#d38b28;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 6,11 20,0"
id="path3795"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#fd8a07;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 6,7 20,7"
id="path3797"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#fe7a15;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="M 6,3 26,3"
id="path3799"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -6,24 +6,8 @@
<link rel="shortcut icon" href="img/stackedit-32.ico" <link rel="shortcut icon" href="img/stackedit-32.ico"
type="image/x-icon"> type="image/x-icon">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Bootstrap -->
<link href="css/bootstrap.css" rel="stylesheet" media="screen">
<link href="css/jgrowl.css" rel="stylesheet" media="screen">
<link href="css/main.css" rel="stylesheet" media="screen"> <link href="css/main.css" rel="stylesheet" media="screen">
<script type="text/javascript" src="js/base64.js"></script> <script data-main="js/main" src="js/require.js"></script>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/jquery.jgrowl.js"></script>
<script type="text/javascript" src="js/jquery-ui.custom.js"></script>
<script type="text/javascript" src="js/jquery.layout.js"></script>
<script type="text/javascript" src="js/bootstrap.js"></script>
<script type="text/javascript" src="js/Markdown.Converter.js"></script>
<script type="text/javascript" src="js/Markdown.Sanitizer.js"></script>
<script type="text/javascript" src="js/Markdown.Editor.js"></script>
<script type="text/javascript" src="js/main.js"></script>
<script type="text/javascript" src="js/async-runner.js"></script>
<script type="text/javascript" src="js/gdrive.js"></script>
<script type="text/javascript" src="js/synchronizer.js"></script>
<script type="text/javascript" src="js/config.custo.js"></script>
<script> <script>
(function(i, s, o, g, r, a, m) { (function(i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r; i['GoogleAnalyticsObject'] = r;
@ -48,13 +32,13 @@
<ul class="nav"> <ul class="nav">
<li><div id="wmd-button-bar"></div></li> <li><div id="wmd-button-bar"></div></li>
</ul> </ul>
<ul class="pull-right" id="menu-bar"> <ul class="pull-right hide" id="menu-bar">
<li class="btn-group"><button class="btn action-force-sync" <li class="btn-group"><button class="btn action-force-sync"
title="Synchronize"> title="Synchronize">
<i class="icon-refresh"></i> <i class="icon-refresh"></i>
</button></li> </button></li>
<li class="btn-group"><button class="btn action-create-file" <li class="btn-group"><button class="btn action-create-file"
title="Create a local file"> title="Create a new local file">
<i class="icon-file"></i> <i class="icon-file"></i>
</button> </button>
<button class="btn" title="Delete the current file locally" <button class="btn" title="Delete the current file locally"
@ -78,22 +62,38 @@
title="Download the current file as HTML"><i title="Download the current file as HTML"><i
class="icon-download-alt"></i> Download as HTML</a></li> class="icon-download-alt"></i> Download as HTML</a></li>
<li class="divider"></li> <li class="divider"></li>
<li><a class="action-upload-gdrive" href="#" <li class="dropdown-submenu"><a href="#"><i
title="Save the current file on Google Drive and synchonize it"><i class="icon-gdrive"></i> Google Drive</a>
class="icon-gdrive"></i> Synchronize on Google Drive</a></li> <ul class="dropdown-menu">
<li><a class="action-upload-dropbox" href="#" <li><a class="action-upload-gdrive" href="#"
title="Save the current file on Dropbox and synchonize it"><i title="Export the current file to Google Drive and synchonize it">Export
class="icon-dropbox"></i> Synchronize on Dropbox</a></li> to Google Drive</a></li>
<li><a href="#" data-toggle="modal" data-target="#modal-download-gdrive"
title="Import an existing file from Google Drive and synchonize it">Import
from Google Drive</a></li>
</ul></li>
<li class="dropdown-submenu"><a href="#"><i
class="icon-dropbox"></i> Dropbox</a>
<ul class="dropdown-menu">
<li><a class="action-upload-dropbox" href="#"
title="Export the current file to Dropbox and synchonize it">Export
to Dropbox</a></li>
<li><a class="action-download-dropbox" href="#"
title="Import an existing file from Dropbox and synchonize it">Import
from Dropbox</a></li>
</ul></li>
<li><a href="#" <li><a href="#"
title="Change the current file synchronized locations" title="Change the current file synchronized locations"
data-toggle="modal" data-target="#modal-manage-sync" data-toggle="modal" data-target="#modal-manage-sync"><i
class="action-refresh-manage-sync"><i class="icon-refresh"></i> class="icon-refresh"></i> Manage synchronization</a></li>
Manage synchronization</a></li>
<li class="divider"></li> <li class="divider"></li>
<li><a href="#" title="Modify your preferences" <li><a href="#" title="Modify your preferences"
data-toggle="modal" data-target="#modal-settings" data-toggle="modal" data-target="#modal-settings"
class="action-load-settings"><i class="icon-cog"></i> class="action-load-settings"><i class="icon-cog"></i>
Settings</a></li> Settings</a></li>
<li><a href="#" data-toggle="modal"
data-target="#modal-about"><i class="icon-question-sign"></i>
About</a></li>
</ul></li> </ul></li>
</ul> </ul>
<ul class="nav pull-right"> <ul class="nav pull-right">
@ -105,9 +105,9 @@
</ul> </ul>
</div> </div>
</div> </div>
<textarea id="wmd-input" class="ui-layout-center"></textarea> <textarea id="wmd-input" class="ui-layout-center hide"></textarea>
<div class="ui-layout-east"></div> <div class="ui-layout-east hide"></div>
<div class="ui-layout-south"></div> <div class="ui-layout-south hide"></div>
<div id="modal-remove-file-confirm" class="modal hide"> <div id="modal-remove-file-confirm" class="modal hide">
<div class="modal-header"> <div class="modal-header">
@ -127,6 +127,23 @@
</div> </div>
</div> </div>
<div id="modal-download-gdrive" class="modal hide">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-hidden="true">&times;</button>
<h3>Import</h3>
</div>
<div class="modal-body">
<p>Please provide a Google Drive file ID:
</p>
<p><input type="text" class="border span6 gdrive-fileid" placeholder="File ID"></input></p>
</div>
<div class="modal-footer">
<a href="#" class="btn" data-dismiss="modal">Cancel</a> <a href="#"
class="btn btn-primary action-download-gdrive" data-dismiss="modal">OK</a>
</div>
</div>
<div id="modal-manage-sync" class="modal hide"> <div id="modal-manage-sync" class="modal hide">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" <button type="button" class="close" data-dismiss="modal"
@ -177,5 +194,65 @@
class="btn btn-primary action-apply-settings" data-dismiss="modal">OK</a> class="btn btn-primary action-apply-settings" data-dismiss="modal">OK</a>
</div> </div>
</div> </div>
<div id="modal-about" class="modal hide">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-hidden="true">&times;</button>
<img src="img/stackedit-promo.png" />
</div>
<div class="modal-body">
<dl>
<dt>About:</dt>
<dd>
<a href="https://github.com/benweet/stackedit/">GitHub page</a>
</dd>
<dd>
<a
href="https://chrome.google.com/webstore/detail/stackedit/iiooodelglhkcpgbajoejffhijaclcdg">Chrome
app</a>
</dd>
<dd>
<a href="https://twitter.com/stackedit/">Follow on Twitter</a>
</dd>
<dd>
<a href="https://twitter.com/stackedit/">Follow on Google+</a>
</dd>
</dl>
<dl>
<dt>Developer:</dt>
<dd>
<a href="http://www.benoitschweblin.com">Benoit Schweblin</a>
<dd>
</dl>
<dl>
<dt>Credits:</dt>
<dd>
<a href="http://jquery.com/">jQuery</a>
</dd>
<dd>
<a href="http://twitter.github.com/bootstrap/">Bootstrap</a>
</dd>
<dd>
<a href="http://requirejs.org/">RequireJS</a>
</dd>
<dd>
<a href="https://code.google.com/p/pagedown/">PageDown</a>
</dd>
<dd>
<a href="http://layout.jquery-dev.net/">UI Layout</a>
</dd>
<dd>
<a href="https://github.com/stanlemon/jGrowl/">jGrowl</a>
</dd>
</dl>
<p>Copyright 2013 <a href="http://www.benoitschweblin.com">Benoit
Schweblin</a><br /> Licensed under an <a
href="http://www.apache.org/licenses/LICENSE-2.0">Apache License</a></p>
</div>
<div class="modal-footer">
<a href="#" class="btn btn-primary" data-dismiss="modal">Close</a>
</div>
</div>
</body> </body>
</html> </html>

View File

@ -1,20 +1,19 @@
var ASYNC_TASK_DEFAULT_TIMEOUT = 30000;
/** /**
* Used to run any asynchronous tasks sequentially (ajax mainly) * Used to run asynchronous tasks sequentially (ajax mainly)
* An asynchronous task must be created with: * An asynchronous task must be created with:
* - a required run() function that may call success(), error() or retry() * - a required run() function that may call success(), error() or retry()
* - an optional onSuccess() function * - an optional onSuccess() function
* - an optional onError() function * - an optional onError() function
* - an optional timeout field (default is 30000) * - an optional timeout field (default is 30000)
*/ */
var asyncTaskRunner = (function() { define(["core"], function(core) {
var asyncTaskRunner = {}; var asyncTaskRunner = {};
var asyncTaskQueue = []; var asyncTaskQueue = [];
var currentTask = undefined; var currentTask = undefined;
var currentTaskRunning = false; var currentTaskRunning = false;
var currentTaskStartTime = currentTime; var currentTaskStartTime = core.currentTime;
// Run the next task in the queue if any and no other is running // Run the next task in the queue if any and no other is running
asyncTaskRunner.runTask = function() { asyncTaskRunner.runTask = function() {
@ -23,7 +22,7 @@ var asyncTaskRunner = (function() {
if(currentTaskRunning === true) { if(currentTaskRunning === true) {
// If the current task takes too long // If the current task takes too long
var timeout = currentTask.timeout || ASYNC_TASK_DEFAULT_TIMEOUT; var timeout = currentTask.timeout || ASYNC_TASK_DEFAULT_TIMEOUT;
if(currentTaskStartTime + timeout < currentTime) { if(currentTaskStartTime + timeout < core.currentTime) {
currentTask.error(); currentTask.error();
} }
return; return;
@ -37,8 +36,8 @@ var asyncTaskRunner = (function() {
// Dequeue an enqueued task // Dequeue an enqueued task
currentTask = asyncTaskQueue.shift(); currentTask = asyncTaskQueue.shift();
currentTaskStartTime = currentTime; currentTaskStartTime = core.currentTime;
showWorkingIndicator(true); core.showWorkingIndicator(true);
// Set task attributes and functions // Set task attributes and functions
currentTask.finished = false; currentTask.finished = false;
@ -48,7 +47,7 @@ var asyncTaskRunner = (function() {
currentTask = undefined; currentTask = undefined;
currentTaskRunning = false; currentTaskRunning = false;
if(asyncTaskQueue.length === 0) { if(asyncTaskQueue.length === 0) {
showWorkingIndicator(false); core.showWorkingIndicator(false);
} }
else { else {
asyncTaskRunner.runTask(); asyncTaskRunner.runTask();
@ -71,14 +70,14 @@ var asyncTaskRunner = (function() {
// Implement an exponential backoff // Implement an exponential backoff
var delay = (Math.pow(2, currentTask.retryCounter++) + Math.random()) * 1000; var delay = (Math.pow(2, currentTask.retryCounter++) + Math.random()) * 1000;
console.log(delay); console.log(delay);
currentTaskStartTime = currentTime + delay; currentTaskStartTime = core.currentTime + delay;
currentTaskRunning = false; currentTaskRunning = false;
asyncTaskRunner.runTask(); asyncTaskRunner.runTask();
}; };
} }
// Run the task // Run the task
if(currentTaskStartTime <= currentTime) { if(currentTaskStartTime <= core.currentTime) {
currentTaskRunning = true; currentTaskRunning = true;
currentTask.run(); currentTask.run();
} }
@ -97,7 +96,7 @@ var asyncTaskRunner = (function() {
currentTask = undefined; currentTask = undefined;
currentTaskRunning = false; currentTaskRunning = false;
if(asyncTaskQueue.length === 0) { if(asyncTaskQueue.length === 0) {
showWorkingIndicator(false); core.showWorkingIndicator(false);
} }
else { else {
asyncTaskRunner.runTask(); asyncTaskRunner.runTask();
@ -111,5 +110,5 @@ var asyncTaskRunner = (function() {
}; };
return asyncTaskRunner; return asyncTaskRunner;
})(); });

View File

@ -1,176 +0,0 @@
/*
* Copyright (c) 2010 Nick Galbreath
* http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/* base64 encode/decode compatible with window.btoa/atob
*
* window.atob/btoa is a Firefox extension to convert binary data (the "b")
* to base64 (ascii, the "a").
*
* It is also found in Safari and Chrome. It is not available in IE.
*
* if (!window.btoa) window.btoa = base64.encode
* if (!window.atob) window.atob = base64.decode
*
* The original spec's for atob/btoa are a bit lacking
* https://developer.mozilla.org/en/DOM/window.atob
* https://developer.mozilla.org/en/DOM/window.btoa
*
* window.btoa and base64.encode takes a string where charCodeAt is [0,255]
* If any character is not [0,255], then an DOMException(5) is thrown.
*
* window.atob and base64.decode take a base64-encoded string
* If the input length is not a multiple of 4, or contains invalid characters
* then an DOMException(5) is thrown.
*/
var base64 = {};
base64.PADCHAR = '=';
base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
base64.makeDOMException = function() {
// sadly in FF,Safari,Chrome you can't make a DOMException
var e, tmp;
try {
return new DOMException(DOMException.INVALID_CHARACTER_ERR);
} catch (tmp) {
// not available, just passback a duck-typed equiv
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error/prototype
var ex = new Error("DOM Exception 5");
// ex.number and ex.description is IE-specific.
ex.code = ex.number = 5;
ex.name = ex.description = "INVALID_CHARACTER_ERR";
// Safari/Chrome output format
ex.toString = function() { return 'Error: ' + ex.name + ': ' + ex.message; };
return ex;
}
}
base64.getbyte64 = function(s,i) {
// This is oddly fast, except on Chrome/V8.
// Minimal or no improvement in performance by using a
// object with properties mapping chars to value (eg. 'A': 0)
var idx = base64.ALPHA.indexOf(s.charAt(i));
if (idx === -1) {
throw base64.makeDOMException();
}
return idx;
}
base64.decode = function(s) {
// convert to string
s = '' + s;
var getbyte64 = base64.getbyte64;
var pads, i, b10;
var imax = s.length
if (imax === 0) {
return s;
}
if (imax % 4 !== 0) {
throw base64.makeDOMException();
}
pads = 0
if (s.charAt(imax - 1) === base64.PADCHAR) {
pads = 1;
if (s.charAt(imax - 2) === base64.PADCHAR) {
pads = 2;
}
// either way, we want to ignore this last block
imax -= 4;
}
var x = [];
for (i = 0; i < imax; i += 4) {
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
(getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
}
switch (pads) {
case 1:
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6);
x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
break;
case 2:
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
x.push(String.fromCharCode(b10 >> 16));
break;
}
return x.join('');
}
base64.getbyte = function(s,i) {
var x = s.charCodeAt(i);
if (x > 255) {
throw base64.makeDOMException();
}
return x;
}
base64.encode = function(s) {
if (arguments.length !== 1) {
throw new SyntaxError("Not enough arguments");
}
var padchar = base64.PADCHAR;
var alpha = base64.ALPHA;
var getbyte = base64.getbyte;
var i, b10;
var x = [];
// convert to string
s = '' + s;
var imax = s.length - s.length % 3;
if (s.length === 0) {
return s;
}
for (i = 0; i < imax; i += 3) {
b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
x.push(alpha.charAt(b10 >> 18));
x.push(alpha.charAt((b10 >> 12) & 0x3F));
x.push(alpha.charAt((b10 >> 6) & 0x3f));
x.push(alpha.charAt(b10 & 0x3f));
}
switch (s.length - imax) {
case 1:
b10 = getbyte(s,i) << 16;
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
padchar + padchar);
break;
case 2:
b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
alpha.charAt((b10 >> 6) & 0x3f) + padchar);
break;
}
return x.join('');
}

18
js/config.js Normal file
View File

@ -0,0 +1,18 @@
var GOOGLE_SCOPES = [ 'https://www.googleapis.com/auth/drive.install',
'https://www.googleapis.com/auth/drive' ];
var DEFAULT_FILE_TITLE = "Title";
var GDRIVE_DEFAULT_FILE_TITLE = "New Markdown document";
var CHECK_ONLINE_PERIOD = 60000;
var AJAX_TIMEOUT = 5000;
var ASYNC_TASK_DEFAULT_TIMEOUT = 30000;
var AUTH_POPUP_TIMEOUT = 90000;
var SYNC_PERIOD = 60000;
var SYNC_PROVIDER_GDRIVE = "sync.gdrive.";
// Use by Google's client.js
var delayedFunction = undefined;
function runDelayedFunction() {
if (delayedFunction !== undefined) {
delayedFunction();
}
}

280
js/core.js Normal file
View File

@ -0,0 +1,280 @@
define(["jquery", "bootstrap", "jgrowl", "layout", "Markdown.Editor"], function($) {
var core = {};
// Time shared by others modules
core.currentTime = new Date().getTime();
core.updateCurrentTime = function() {
core.currentTime = new Date().getTime();
};
// Usage: callback = callback || core.doNothing;
core.doNothing = function() {};
// Used by asyncTaskRunner
core.showWorkingIndicator = function(show) {
if (show === false) {
$(".working-indicator").addClass("hide");
} else {
$(".working-indicator").removeClass("hide");
}
};
// Used to show a notification message
core.showMessage = function(msg, iconClass, options) {
options = options || {};
iconClass = iconClass || "icon-info-sign";
$.jGrowl("<i class='icon-white " + iconClass + "'></i> " + msg, options);
};
// Used to show an error message
core.showError = function(msg) {
core.showMessage(msg, "icon-warning-sign");
};
// Offline management
core.isOffline = false;
var offlineTime = core.currentTime;
var offlineListeners = [];
core.addOfflineListener = function(listener) {
offlineListeners.push(listener);
};
core.setOffline = function() {
offlineTime = core.currentTime;
if(core.isOffline === false) {
core.isOffline = true;
core.showMessage("You are offline.", "icon-exclamation-sign msg-offline", {
sticky : true,
close : function() {
core.showMessage("You are back online!", "icon-signal");
}
});
for(var i=0; i<offlineListeners.length; i++) {
offlineListeners[i]();
}
}
};
core.setOnline = function() {
if(offline === true) {
$(".msg-offline").parents(".jGrowl-notification").trigger(
'jGrowl.beforeClose');
core.isOffline = false;
for(var i=0; i<offlineListeners.length; i++) {
offlineListeners[i]();
}
}
};
core.checkOnline = function() {
// Try to reconnect if we are offline but we have some network
if (core.isOffline === true && navigator.onLine === true
&& offlineTime + CHECK_ONLINE_PERIOD < core.currentTime) {
offlineTime = core.currentTime;
// Try to download anything to test the connection
$.ajax({
url : "https://apis.google.com/js/client.js",
timeout : AJAX_TIMEOUT, dataType : "script"
}).done(function() {
core.setOnline();
});
}
};
// Setting management
var settings = { layoutOrientation : "horizontal" };
core.loadSettings = function() {
if (localStorage.settings) {
$.extend(settings, JSON.parse(localStorage.settings));
}
// Layout orientation
$("input:radio[name=radio-layout-orientation][value="
+ settings.layoutOrientation + "]").prop("checked", true);
};
core.saveSettings = function() {
// Layout orientation
settings.layoutOrientation = $(
"input:radio[name=radio-layout-orientation]:checked").prop("value");
localStorage.settings = JSON.stringify(settings);
};
// Create the layout
core.createLayout = function() {
var layout = undefined;
var layoutGlobalConfig = {
closable : true,
resizable : false,
slidable : false,
livePaneResizing : true,
enableCursorHotkey : false,
spacing_open : 15,
spacing_closed : 15,
togglerLength_open : 90,
togglerLength_closed : 90,
center__minWidth : 100,
center__minHeight : 100,
stateManagement__enabled : false
};
if (settings.layoutOrientation == "horizontal") {
$(".ui-layout-south").remove();
$(".ui-layout-east").addClass("well").prop("id", "wmd-preview");
layout = $('body').layout(
$.extend(layoutGlobalConfig, {
east__resizable : true,
east__size : .5,
east__minSize : 200
})
);
} else if (settings.layoutOrientation == "vertical") {
$(".ui-layout-east").remove();
$(".ui-layout-south").addClass("well").prop("id", "wmd-preview");
layout = $('body').layout(
$.extend(layoutGlobalConfig, {
south__resizable : true,
south__size : .5,
south__minSize : 200
})
);
}
$(".ui-layout-toggler-north").addClass("btn").append(
$("<b>").addClass("caret"));
$(".ui-layout-toggler-south").addClass("btn").append(
$("<b>").addClass("caret"));
$(".ui-layout-toggler-east").addClass("btn").append(
$("<b>").addClass("caret"));
$("#navbar").click(function() {
layout.allowOverflow('north');
});
};
// Create the PageDown editor
core.createEditor = function(onTextChange) {
$("#wmd-button-bar").empty();
var converter = Markdown.getSanitizingConverter();
var firstChange = true;
converter.hooks.chain("preConversion", function(text) {
if (!firstChange) {
onTextChange();
}
return text;
});
var editor = new Markdown.Editor(converter);
editor.run();
firstChange = false;
$(".wmd-button-row").addClass("btn-group").find("li:not(.wmd-spacer)")
.addClass("btn").css("left", 0).find("span").hide();
$("#wmd-bold-button").append($("<i>").addClass("icon-bold"));
$("#wmd-italic-button").append($("<i>").addClass("icon-italic"));
$("#wmd-link-button").append($("<i>").addClass("icon-globe"));
$("#wmd-quote-button").append($("<i>").addClass("icon-indent-left"));
$("#wmd-code-button").append($("<i>").addClass("icon-code"));
$("#wmd-image-button").append($("<i>").addClass("icon-picture"));
$("#wmd-olist-button").append($("<i>").addClass("icon-numbered-list"));
$("#wmd-ulist-button").append($("<i>").addClass("icon-list"));
$("#wmd-heading-button").append($("<i>").addClass("icon-text-height"));
$("#wmd-hr-button").append($("<i>").addClass("icon-hr"));
$("#wmd-undo-button").append($("<i>").addClass("icon-undo"));
$("#wmd-redo-button").append($("<i>").addClass("icon-share-alt"));
};
// Base64 conversion
core.encodeBase64 = function(str) {
if (str.length === 0) {
return "";
}
// UTF-8 to byte array
var bytes = [], offset = 0, length, char;
str = encodeURI(str);
length = str.length;
while (offset < length) {
char = str[offset];
offset += 1;
if ('%' !== char) {
bytes.push(char.charCodeAt(0));
} else {
char = str[offset] + str[offset + 1];
bytes.push(parseInt(char, 16));
offset += 2;
}
}
// byte array to base64
var padchar = '=';
var alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var i, b10;
var x = [];
var imax = bytes.length - bytes.length % 3;
for (i = 0; i < imax; i += 3) {
b10 = (bytes[i] << 16) | (bytes[i+1] << 8) | bytes[i+2];
x.push(alpha.charAt(b10 >> 18));
x.push(alpha.charAt((b10 >> 12) & 0x3F));
x.push(alpha.charAt((b10 >> 6) & 0x3f));
x.push(alpha.charAt(b10 & 0x3f));
}
switch (bytes.length - imax) {
case 1:
b10 = bytes[i] << 16;
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
padchar + padchar);
break;
case 2:
b10 = (bytes[i] << 16) | (bytes[i+1] << 8);
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
alpha.charAt((b10 >> 6) & 0x3f) + padchar);
break;
}
return x.join('');
};
core.init = function() {
// jGrowl configuration
$.jGrowl.defaults.life = 5000;
$.jGrowl.defaults.closer = false;
$.jGrowl.defaults.closeTemplate = '';
$.jGrowl.defaults.position = 'bottom-right';
// listen to online/offline events
$(window).on('offline', core.setOffline);
$(window).on('online', core.setOnline);
if (navigator.onLine === false) {
core.setOffline();
}
// Avoid dropdown to close when clicking on submenu
$('.dropdown-submenu > a').click(function(e) {
e.stopPropagation();
});
$("#menu-bar, .ui-layout-center, .ui-layout-east, .ui-layout-south").removeClass("hide");
this.loadSettings();
this.createLayout();
$(".action-load-settings").click(function() {
core.loadSettings();
});
$(".action-apply-settings").click(function() {
core.saveSettings();
location.reload();
});
};
return core;
});

339
js/file-manager.js Normal file
View File

@ -0,0 +1,339 @@
define(["jquery", "core", "gdrive", "synchronizer", "async-runner"], function($, core, gdrive, synchronizer, asyncTaskRunner) {
var fileManager = {};
fileManager.init = function() {
gdrive.init(fileManager);
var changeSyncButtonState = function() {
if(synchronizer.isRunning() || synchronizer.isQueueEmpty() || core.isOffline) {
$(".action-force-sync").addClass("disabled");
}
else {
$(".action-force-sync").removeClass("disabled");
}
};
core.addOfflineListener(changeSyncButtonState);
synchronizer.init(fileManager, {
onSyncBegin : changeSyncButtonState,
onSyncEnd : changeSyncButtonState,
onQueueChanged : changeSyncButtonState
});
$(".action-force-sync").click(function() {
if(!$(this).hasClass("disabled")) {
synchronizer.forceSync();
}
});
fileManager.selectFile();
// Do periodic tasks
window.setInterval(function() {
core.updateCurrentTime();
synchronizer.sync();
asyncTaskRunner.runTask();
core.checkOnline();
}, 1000);
$(".action-create-file").click(function() {
var fileIndex = fileManager.createFile();
fileManager.selectFile(fileIndex);
$("#file-title").click();
});
$(".action-remove-file").click(function() {
fileManager.deleteFile();
fileManager.selectFile();
});
$("#file-title").click(function() {
$(this).hide();
$("#file-title-input").show().focus();
});
$("#file-title-input").blur(function() {
var title = $.trim($(this).val());
if (title) {
var fileIndexTitle = localStorage["file.current"] + ".title";
if (title != localStorage[fileIndexTitle]) {
localStorage[fileIndexTitle] = title;
fileManager.updateFileTitles();
fileManager.saveFile();
}
}
$(this).hide();
$("#file-title").show();
});
$(".action-download-md").click(
function() {
var content = $("#wmd-input").val();
var uriContent = "data:application/octet-stream;base64,"
+ core.encodeBase64(content);
window.open(uriContent, 'file');
});
$(".action-download-html").click(
function() {
var content = $("#wmd-preview").html();
var uriContent = "data:application/octet-stream;base64,"
+ core.encodeBase64(content);
window.open(uriContent, 'file');
});
$(".action-upload-gdrive").click(uploadGdrive);
$(".action-download-gdrive").click(function() {
var fileId = $(".gdrive-fileid").val();
$(".gdrive-fileid").val("");
downloadGdrive(fileId);
});
$(".action-download-dropbox").click(function() {
core.showMessage("Sorry, Dropbox synchronization is not yet available.");
});
$(".action-upload-dropbox").click(function() {
core.showMessage("Sorry, Dropbox synchronization is not yet available.");
});
};
// Caution: this function recreate the editor (reset undo operations)
var fileDescList = [];
fileManager.selectFile = function(fileIndex) {
// If file system does not exist
if (!localStorage["file.counter"] || !localStorage["file.list"]) {
localStorage.clear();
localStorage["file.counter"] = 0;
localStorage["file.list"] = ";";
}
// If no file create one
if (localStorage["file.list"].length === 1) {
fileIndex = this.createFile();
}
fileIndex = fileIndex || localStorage["file.current"];
if(fileIndex !== undefined) {
localStorage["file.current"] = fileIndex;
}
refreshManageSync();
// Update the file titles
this.updateFileTitles();
// Recreate the editor
var fileIndex = localStorage["file.current"];
$("#wmd-input").val(localStorage[fileIndex + ".content"]);
core.createEditor(function() {
fileManager.saveFile();
});
};
fileManager.createFile = function(title, content, syncIndexes) {
content = content || "";
syncIndexes = syncIndexes || [];
if (!title) {
// Create a file title
title = DEFAULT_FILE_TITLE;
function exists(title) {
for ( var i = 0; i < fileDescList.length; i++) {
if(fileDescList[i].title == title) {
return true;
}
}
}
var indicator = 2;
while(exists(title)) {
title = DEFAULT_FILE_TITLE + indicator++;
}
}
// Create the fileIndex
var fileCounter = parseInt(localStorage["file.counter"]);
var fileIndex = "file." + fileCounter;
// Create the file in the localStorage
localStorage[fileIndex + ".content"] = content;
localStorage[fileIndex + ".title"] = title;
var sync = ";";
for(var i=0; i<syncIndexes.length; i++) {
sync += syncIndexes[i] + ";";
}
localStorage[fileIndex + ".sync"] = sync;
localStorage["file.counter"] = fileCounter + 1;
localStorage["file.list"] += fileIndex + ";";
return fileIndex;
};
fileManager.deleteFile = function(fileIndex) {
var fileIndexCurrent = localStorage["file.current"];
fileIndex = fileIndex || fileIndexCurrent;
if(fileIndex == fileIndexCurrent) {
localStorage.removeItem("file.current");
}
// Remove synchronized locations
var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";");
for ( var i = 1; i < fileSyncIndexList.length - 1; i++) {
var fileSyncIndex = fileSyncIndexList[i];
fileManager.removeSync(fileSyncIndex);
}
localStorage.removeItem(fileIndex + ".sync");
localStorage["file.list"] = localStorage["file.list"].replace(";"
+ fileIndex + ";", ";");
localStorage.removeItem(fileIndex + ".title");
localStorage.removeItem(fileIndex + ".content");
};
fileManager.saveFile = function() {
var content = $("#wmd-input").val();
var fileIndex = localStorage["file.current"];
localStorage[fileIndex + ".content"] = content;
synchronizer.addFileForUpload(fileIndex);
};
fileManager.updateFileTitles = function() {
fileDescList = [];
$("#file-selector").empty();
var fileIndexList = localStorage["file.list"].split(";");
for ( var i = 1; i < fileIndexList.length - 1; i++) {
var fileIndex = fileIndexList[i];
var title = localStorage[fileIndex + ".title"];
fileDescList.push({ index : fileIndex, title : title });
}
fileDescList.sort(function(a, b) {
if (a.title.toLowerCase() < b.title.toLowerCase())
return -1;
if (a.title.toLowerCase() > b.title.toLowerCase())
return 1;
return 0;
});
var fileIndex = localStorage["file.current"];
// If no default file take first one
if (!fileIndex) {
fileIndex = fileDescList[0].index;
localStorage["file.current"] = fileIndex;
}
var useGoogleDrive = false;
function composeTitle(fileIndex) {
var result = localStorage[fileIndex + ".title"];
var sync = localStorage[fileIndex + ".sync"];
if (sync.indexOf(";" + SYNC_PROVIDER_GDRIVE) !== -1) {
useGoogleDrive = true;
result = '<i class="icon-gdrive"></i> ' + result;
}
return result;
}
synchronizer.useGoogleDrive = useGoogleDrive;
// Update the file title
var title = localStorage[fileIndex + ".title"];
document.title = "StackEdit - " + title;
$("#file-title").html(composeTitle(fileIndex));
$(".file-title").text(title);
$("#file-title-input").val(title);
// Update the file selector
$("#file-selector").empty();
for ( var i = 0; i < fileDescList.length; i++) {
var fileDesc = fileDescList[i];
var a = $("<a>").html(composeTitle(fileDesc.index));
var li = $("<li>").append(a);
if (fileDesc.index == fileIndex) {
li.addClass("disabled");
} else {
a.prop("href", "#").click((function(fileIndex) {
return function() {
localStorage["file.current"] = fileIndex;
fileManager.selectFile();
};
})(fileDesc.index));
}
$("#file-selector").append(li);
}
};
// Remove a synchronized location
fileManager.removeSync = function(fileSyncIndex) {
var fileIndexCurrent = localStorage["file.current"];
var fileIndex = this.getFileIndexFromSync(fileSyncIndex);
if(fileIndex !== undefined) {
localStorage[fileIndex + ".sync"] = localStorage[fileIndex + ".sync"].replace(";"
+ fileSyncIndex + ";", ";");
if(fileIndex == fileIndexCurrent) {
refreshManageSync();
}
}
// Remove etag
localStorage.removeItem(fileSyncIndex + ".etag");
};
// Look for local file associated to a synchronized location
fileManager.getFileIndexFromSync = function(fileSyncIndex) {
var fileIndex = undefined;
var fileIndexList = localStorage["file.list"].split(";");
for ( var i = 1; i < fileIndexList.length - 1; i++) {
var tempFileIndex = fileIndexList[i];
var sync = localStorage[tempFileIndex + ".sync"];
if (sync.indexOf(";" + fileSyncIndex + ";") !== -1) {
fileIndex = tempFileIndex;
break;
}
}
return fileIndex;
};
function uploadGdrive() {
var fileIndex = localStorage["file.current"];
var content = localStorage[fileIndex + ".content"];
var title = localStorage[fileIndex + ".title"];
gdrive.createFile(title, content, function(fileSyncIndex) {
if (fileSyncIndex) {
localStorage[fileIndex + ".sync"] += fileSyncIndex + ";";
refreshManageSync();
fileManager.updateFileTitles();
core.showMessage('"' + title
+ '" will now be synchronized on Google Drive.');
}
});
}
function downloadGdrive(fileId) {
fileId = fileId.trim();
if(fileId.length === 0) {
return;
}
gdrive.downloadMetadata([fileId], function(result) {
if(result === undefined || result.length === 0) {
return;
}
});
}
function refreshManageSync() {
var fileIndex = localStorage["file.current"];
var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";");
$(".msg-no-sync, .msg-sync-list").addClass("hide");
$("#manage-sync-list .input-append").remove();
if (fileSyncIndexList.length > 2) {
$(".msg-sync-list").removeClass("hide");
} else {
$(".msg-no-sync").removeClass("hide");
}
for ( var i = 1; i < fileSyncIndexList.length - 1; i++) {
var fileSyncIndex = fileSyncIndexList[i];
(function(fileSyncIndex) {
var line = $("<div>").addClass("input-prepend input-append");
if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
line.append($("<span>").addClass("add-on").html(
'<i class="icon-gdrive"></i>'));
line.append($("<input>").prop("type", "text").prop(
"disabled", true).addClass("span5").val(
"FileID="
+ fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length)));
}
line.append($("<a>").addClass("btn").html(
'<i class="icon-trash"></i>').prop("title",
"Remove this synchronized location").click(function() {
fileManager.removeSync(fileSyncIndex);
fileManager.updateFileTitles();
}));
$("#manage-sync-list").append(line);
})(fileSyncIndex);
}
}
return fileManager;
});

View File

@ -1,52 +1,40 @@
var GOOGLE_CLIENT_ID = '241271498917-jpto9lls9fqnem1e4h6ppds9uob8rpvu.apps.googleusercontent.com'; define(["jquery", "core", "async-runner"], function($, core, asyncTaskRunner) {
var SCOPES = [ 'https://www.googleapis.com/auth/drive.install',
'https://www.googleapis.com/auth/drive.file' ];
var AUTH_POPUP_TIMEOUT = 90000;
var DEFAULT_GDRIVE_FILE_TITLE = "New Markdown document";
var gdriveDelayedFunction = undefined; // Dependencies
function runGdriveDelayedFunction() { var fileManager = undefined;
if (gdriveDelayedFunction !== undefined) {
gdriveDelayedFunction();
}
}
var gdrive = (function($) {
var connected = false; var connected = false;
var authenticated = false; var authenticated = false;
var doNothing = function() {
};
var gdrive = {}; var gdrive = {};
// Try to connect Gdrive by downloading client.js // Try to connect Gdrive by downloading client.js
function connect(callback) { function connect(callback) {
callback = callback || doNothing; callback = callback || core.doNothing;
var asyncTask = {}; var asyncTask = {};
asyncTask.run = function() { asyncTask.run = function() {
if (connected === true) { if (connected === true) {
asyncTask.success(); asyncTask.success();
return; return;
} }
gdriveDelayedFunction = function() { delayedFunction = function() {
asyncTask.success(); asyncTask.success();
}; };
$.ajax({ $.ajax({
url : "https://apis.google.com/js/client.js?onload=runGdriveDelayedFunction", url : "https://apis.google.com/js/client.js?onload=runDelayedFunction",
dataType : "script", timeout : AJAX_TIMEOUT dataType : "script", timeout : AJAX_TIMEOUT
}).fail(function() { }).fail(function() {
asyncTask.error(); asyncTask.error();
}); });
}; };
asyncTask.onSuccess = function() { asyncTask.onSuccess = function() {
gdriveDelayedFunction = undefined; delayedFunction = undefined;
connected = true; connected = true;
callback(); callback();
}; };
asyncTask.onError = function() { asyncTask.onError = function() {
gdriveDelayedFunction = undefined; delayedFunction = undefined;
onOffline(); core.setOffline();
callback(); callback();
}; };
asyncTaskRunner.addTask(asyncTask); asyncTaskRunner.addTask(asyncTask);
@ -54,7 +42,7 @@ var gdrive = (function($) {
// Try to authenticate with Oauth // Try to authenticate with Oauth
function authenticate(callback, immediate) { function authenticate(callback, immediate) {
callback = callback || doNothing; callback = callback || core.doNothing;
if (immediate === undefined) { if (immediate === undefined) {
immediate = true; immediate = true;
} }
@ -75,10 +63,10 @@ var gdrive = (function($) {
return; return;
} }
if (immediate === false) { if (immediate === false) {
showMessage("Please make sure the Google authorization popup is not blocked by your browser."); core.showMessage("Please make sure the Google authorization popup is not blocked by your browser.");
} }
gapi.auth.authorize({ 'client_id' : GOOGLE_CLIENT_ID, gapi.auth.authorize({ 'client_id' : GOOGLE_CLIENT_ID,
'scope' : SCOPES, 'immediate' : immediate }, function( 'scope' : GOOGLE_SCOPES, 'immediate' : immediate }, function(
authResult) { authResult) {
if (!authResult || authResult.error) { if (!authResult || authResult.error) {
asyncTask.error(); asyncTask.error();
@ -105,38 +93,8 @@ var gdrive = (function($) {
}); });
} }
function handleError(error, asyncTask, callback) {
var errorMsg = undefined;
if (error) {
// Try to analyze the error
if (error.code >= 500 && error.code < 600) {
errorMsg = "Google Drive is not accessible.";
// Retry as described in Google's best practices
asyncTask.retry();
return;
} else if (error.code === 401) {
authenticated = false;
errorMsg = "Access to Google Drive is not authorized.";
} else if (error.code <= 0) {
connected = false;
authenticated = false;
onOffline();
} else {
errorMsg = "Google Drive error (" + error.code + ": "
+ error.message + ").";
}
}
asyncTask.onError = function() {
if (errorMsg !== undefined) {
showError(errorMsg);
}
callback();
};
asyncTask.error();
}
function upload(fileId, parentId, title, content, callback) { function upload(fileId, parentId, title, content, callback) {
callback = callback || doNothing; callback = callback || core.doNothing;
authenticate(function() { authenticate(function() {
if (connected === false) { if (connected === false) {
callback(); callback();
@ -165,7 +123,7 @@ var gdrive = (function($) {
method = 'PUT'; method = 'PUT';
} }
var base64Data = base64.encode(content); var base64Data = core.encodeBase64(content);
var multipartRequestBody = delimiter var multipartRequestBody = delimiter
+ 'Content-Type: application/json\r\n\r\n' + 'Content-Type: application/json\r\n\r\n'
+ JSON.stringify(metadata) + delimiter + 'Content-Type: ' + JSON.stringify(metadata) + delimiter + 'Content-Type: '
@ -189,13 +147,15 @@ var gdrive = (function($) {
return; return;
} }
var error = response.error; var error = response.error;
// If file has been removed from Google Drive // If it's an update and file has been removed from Google Drive
if(error !== undefined && fileId !== undefined && error.code === 404) { if(error !== undefined && fileId !== undefined && error.code === 404) {
showMessage('"' + title + '" has been removed from Google Drive.'); core.showMessage('"' + title + '" has been removed from Google Drive.');
fileManager.removeSync(SYNC_PROVIDER_GDRIVE + fileId); fileManager.removeSync(SYNC_PROVIDER_GDRIVE + fileId);
fileManager.updateFileTitles(); fileManager.updateFileTitles();
// Avoid error analyzed by handleError // We assume it's not error
error = undefined; fileSyncIndex = null;
asyncTask.success();
return;
} }
// Handle error // Handle error
handleError(error, asyncTask, callback); handleError(error, asyncTask, callback);
@ -209,7 +169,7 @@ var gdrive = (function($) {
} }
gdrive.checkUpdates = function(lastChangeId, callback) { gdrive.checkUpdates = function(lastChangeId, callback) {
callback = callback || doNothing; callback = callback || core.doNothing;
authenticate(function() { authenticate(function() {
if (connected === false) { if (connected === false) {
callback(); callback();
@ -263,7 +223,7 @@ var gdrive = (function($) {
}; };
gdrive.downloadMetadata = function(ids, callback, result) { gdrive.downloadMetadata = function(ids, callback, result) {
callback = callback || doNothing; callback = callback || core.doNothing;
result = result || []; result = result || [];
if(ids.length === 0) { if(ids.length === 0) {
callback(result); callback(result);
@ -294,6 +254,9 @@ var gdrive = (function($) {
message: jqXHR.statusText message: jqXHR.statusText
}; };
// Handle error // Handle error
if(error.code === 403) {
error = "File is not available";
}
handleError(error, asyncTask, callback); handleError(error, asyncTask, callback);
}); });
}; };
@ -305,7 +268,7 @@ var gdrive = (function($) {
}; };
gdrive.downloadContent = function(objects, callback, result) { gdrive.downloadContent = function(objects, callback, result) {
callback = callback || doNothing; callback = callback || core.doNothing;
result = result || []; result = result || [];
if(objects.length === 0) { if(objects.length === 0) {
callback(result); callback(result);
@ -361,6 +324,40 @@ var gdrive = (function($) {
}); });
}; };
function handleError(error, asyncTask, callback) {
var errorMsg = undefined;
asyncTask.onError = function() {
if (errorMsg !== undefined) {
core.showError(errorMsg);
}
callback();
};
if (error) {
// Try to analyze the error
if (typeof error === "string") {
errorMsg = error;
}
else if ((error.code >= 500 && error.code < 600) ||
(error.code === 401 && error.message == "Login Required")) { // Sometimes we have this 401 error
errorMsg = "Google Drive is not accessible.";
// Retry as described in Google's best practices
asyncTask.retry();
return;
} else if (error.code === 401 || error.code === 403) {
authenticated = false;
errorMsg = "Access to Google Drive is not authorized.";
} else if (error.code <= 0) {
connected = false;
authenticated = false;
core.setOffline();
} else {
errorMsg = "Google Drive error (" + error.code + ": "
+ error.message + ").";
}
}
asyncTask.error();
}
gdrive.createFile = function(title, content, callback) { gdrive.createFile = function(title, content, callback) {
upload(undefined, undefined, title, content, callback); upload(undefined, undefined, title, content, callback);
}; };
@ -369,7 +366,8 @@ var gdrive = (function($) {
upload(id, undefined, title, content, callback); upload(id, undefined, title, content, callback);
}; };
gdrive.init = function() { gdrive.init = function(fileManagerModule) {
fileManager = fileManagerModule;
var state = localStorage["sync.gdrive.state"]; var state = localStorage["sync.gdrive.state"];
if(state === undefined) { if(state === undefined) {
return; return;
@ -377,14 +375,14 @@ var gdrive = (function($) {
localStorage.removeItem("sync.gdrive.state"); localStorage.removeItem("sync.gdrive.state");
state = JSON.parse(state); state = JSON.parse(state);
if (state.action == "create") { if (state.action == "create") {
upload(undefined, state.folderId, DEFAULT_GDRIVE_FILE_TITLE, upload(undefined, state.folderId, GDRIVE_DEFAULT_FILE_TITLE,
"", function(fileSyncIndex) { "", function(fileSyncIndex) {
if(fileSyncIndex === undefined) { if(fileSyncIndex === undefined) {
return; return;
} }
var fileIndex = fileManager.createFile(DEFAULT_GDRIVE_FILE_TITLE, "", [fileSyncIndex]); var fileIndex = fileManager.createFile(GDRIVE_DEFAULT_FILE_TITLE, "", [fileSyncIndex]);
fileManager.selectFile(fileIndex); fileManager.selectFile(fileIndex);
showMessage('"' + DEFAULT_GDRIVE_FILE_TITLE + '" created successfully on Google Drive.'); core.showMessage('"' + GDRIVE_DEFAULT_FILE_TITLE + '" created successfully on Google Drive.');
}); });
} }
else if (state.action == "open") { else if (state.action == "open") {
@ -413,7 +411,7 @@ var gdrive = (function($) {
localStorage[fileSyncIndex + ".etag"] = file.etag; localStorage[fileSyncIndex + ".etag"] = file.etag;
var fileIndex = fileManager.createFile(file.title, file.content, [fileSyncIndex]); var fileIndex = fileManager.createFile(file.title, file.content, [fileSyncIndex]);
fileManager.selectFile(fileIndex); fileManager.selectFile(fileIndex);
showMessage('"' + file.title + '" imported successfully from Google Drive.'); core.showMessage('"' + file.title + '" imported successfully from Google Drive.');
} }
}); });
}); });
@ -421,4 +419,4 @@ var gdrive = (function($) {
}; };
return gdrive; return gdrive;
})(jQuery); });

View File

@ -1,511 +1,22 @@
var currentTime = new Date().getTime(); requirejs.config({
paths: {
function showError(msg) { custo: [
showMessage(msg, "icon-warning-sign"); 'dev/custo',
} 'prod/custo'
]
function showMessage(msg, iconClass, options) { },
options = options || {}; shim: {
iconClass = iconClass || "icon-info-sign"; 'jquery-ui': ['jquery'],
$.jGrowl("<i class='icon-white " + iconClass + "'></i> " + msg, options); 'bootstrap': ['jquery'],
} 'jgrowl': ['jquery'],
'layout': ['jquery-ui'],
function showWorkingIndicator(show) { 'Markdown.Sanitizer': ['Markdown.Converter'],
if (show === false) { 'Markdown.Editor': ['Markdown.Sanitizer']
$(".working-indicator").addClass("hide"); }
} else { });
$(".working-indicator").removeClass("hide"); require(["jquery", "core", "file-manager", "config", "custo"], function($, core, fileManager) {
}
}
var AJAX_TIMEOUT = 5000;
var CHECK_ONLINE_PERIOD = 60000;
var offline = false;
var offlineTime = currentTime;
var offlineListeners = [];
function onOffline() {
offlineTime = currentTime;
if(offline === false) {
offline = true;
showMessage("You are offline.", "icon-exclamation-sign msg-offline", {
sticky : true, close : function() {
showMessage("You are back online!", "icon-signal");
} });
for(var i=0; i<offlineListeners.length; i++) {
offlineListeners[i]();
}
}
}
function onOnline() {
if(offline === true) {
$(".msg-offline").parents(".jGrowl-notification").trigger(
'jGrowl.beforeClose');
offline = false;
for(var i=0; i<offlineListeners.length; i++) {
offlineListeners[i]();
}
}
}
function checkOnline() {
// Try to reconnect if we are offline but we have some network
if (offline === true && navigator.onLine === true
&& offlineTime + CHECK_ONLINE_PERIOD < currentTime) {
offlineTime = currentTime;
// Try to download anything to test the connection
$.ajax(
{ url : "https://apis.google.com/js/client.js",
timeout : AJAX_TIMEOUT, dataType : "script" }).done(function() {
onOnline();
});
}
}
var DEFAULT_FILE_TITLE = "Filename";
var fileManager = (function($) {
var fileManager = {};
var save = false;
fileManager.init = function() {
gdrive.init();
var changeSyncButtonState = function() {
if(synchronizer.isRunning() || synchronizer.isQueueEmpty() || offline) {
$(".action-force-sync").addClass("disabled");
}
else {
$(".action-force-sync").removeClass("disabled");
}
};
offlineListeners.push(changeSyncButtonState);
synchronizer.init({
onSyncBegin : changeSyncButtonState,
onSyncEnd : changeSyncButtonState,
onQueueChanged : changeSyncButtonState
});
$(".action-force-sync").click(function() {
if(!$(this).hasClass("disabled")) {
synchronizer.forceSync();
}
});
fileManager.selectFile();
// Do periodic stuff
window.setInterval(function() {
currentTime = new Date().getTime();
fileManager.saveFile();
synchronizer.sync();
asyncTaskRunner.runTask();
checkOnline();
}, 1000);
$(".action-create-file").click(function() {
fileManager.saveFile();
var fileIndex = fileManager.createFile();
fileManager.selectFile(fileIndex);
$("#file-title").click();
});
$(".action-remove-file").click(function() {
fileManager.deleteFile();
fileManager.selectFile();
});
$(".action-refresh-manage-sync").click(refreshManageSync);
$("#file-title").click(function() {
$(this).hide();
$("#file-title-input").show().focus();
});
$("#file-title-input").blur(function() {
var title = $.trim($(this).val());
if (title) {
var fileIndexTitle = localStorage["file.current"] + ".title";
if (title != localStorage[fileIndexTitle]) {
localStorage[fileIndexTitle] = title;
fileManager.updateFileTitles();
save = true;
}
}
$(this).hide();
$("#file-title").show();
});
$(".action-download-md").click(
function() {
var content = $("#wmd-input").val();
var uriContent = "data:application/octet-stream;base64,"
+ base64.encode(content);
window.open(uriContent, 'file');
});
$(".action-download-html").click(
function() {
var content = $("#wmd-preview").html();
var uriContent = "data:application/octet-stream;base64,"
+ base64.encode(content);
window.open(uriContent, 'file');
});
$(".action-upload-gdrive").click(uploadGdrive);
$(".action-upload-dropbox").click(function() {
showMessage("Sorry, Dropbox synchronization is not yet available.");
});
};
var fileDescList = [];
fileManager.selectFile = function(fileIndex) {
// If file system does not exist
if (!localStorage["file.counter"] || !localStorage["file.list"]) {
localStorage.clear();
localStorage["file.counter"] = 0;
localStorage["file.list"] = ";";
}
// If no file create one
if (localStorage["file.list"].length === 1) {
fileIndex = this.createFile();
}
fileIndex = fileIndex || localStorage["file.current"];
if(fileIndex !== undefined) {
localStorage["file.current"] = fileIndex;
}
// Update the file titles
this.updateFileTitles();
// Update the editor
var fileIndex = localStorage["file.current"];
$("#wmd-input").val(localStorage[fileIndex + ".content"]);
core.createEditor(function() {
save = true;
});
};
fileManager.createFile = function(title, content, syncIndexes) {
content = content || "";
syncIndexes = syncIndexes || [];
if (!title) {
// Create a file title
title = DEFAULT_FILE_TITLE;
function exists(title) {
for ( var i = 0; i < fileDescList.length; i++) {
if(fileDescList[i].title == title) {
return true;
}
}
}
var indicator = 2;
while(exists(title)) {
title = DEFAULT_FILE_TITLE + indicator++;
}
}
// Create the fileIndex
var fileCounter = parseInt(localStorage["file.counter"]);
var fileIndex = "file." + fileCounter;
// Create the file in the localStorage
localStorage[fileIndex + ".content"] = content;
localStorage[fileIndex + ".title"] = title;
var sync = ";";
for(var i=0; i<syncIndexes.length; i++) {
sync += syncIndexes[i] + ";";
}
localStorage[fileIndex + ".sync"] = sync;
localStorage["file.counter"] = fileCounter + 1;
localStorage["file.list"] += fileIndex + ";";
return fileIndex;
};
fileManager.deleteFile = function() {
var fileIndex = localStorage["file.current"];
// Remove synchronized locations
var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";");
for ( var i = 1; i < fileSyncIndexList.length - 1; i++) {
var fileSyncIndex = fileSyncIndexList[i];
fileManager.removeSync(fileSyncIndex);
}
localStorage.removeItem(fileIndex + ".sync");
localStorage.removeItem("file.current");
localStorage["file.list"] = localStorage["file.list"].replace(";"
+ fileIndex + ";", ";");
localStorage.removeItem(fileIndex + ".title");
localStorage.removeItem(fileIndex + ".content");
};
fileManager.saveFile = function() {
if (save) {
var content = $("#wmd-input").val();
var fileIndex = localStorage["file.current"];
localStorage[fileIndex + ".content"] = content;
synchronizer.addFileForUpload(fileIndex);
save = false;
}
};
fileManager.updateFileTitles = function() {
fileDescList = [];
$("#file-selector").empty();
var fileIndexList = localStorage["file.list"].split(";");
for ( var i = 1; i < fileIndexList.length - 1; i++) {
var fileIndex = fileIndexList[i];
var title = localStorage[fileIndex + ".title"];
fileDescList.push({ index : fileIndex, title : title });
}
fileDescList.sort(function(a, b) {
if (a.title.toLowerCase() < b.title.toLowerCase())
return -1;
if (a.title.toLowerCase() > b.title.toLowerCase())
return 1;
return 0;
});
var fileIndex = localStorage["file.current"];
// If no default file take first one
if (!fileIndex) {
fileIndex = fileDescList[0].index;
localStorage["file.current"] = fileIndex;
}
syncGoogleDrive = false;
function composeTitle(fileIndex) {
var result = localStorage[fileIndex + ".title"];
var sync = localStorage[fileIndex + ".sync"];
if (sync.indexOf(";" + SYNC_PROVIDER_GDRIVE) !== -1) {
syncGoogleDrive = true;
result = '<i class="icon-gdrive"></i> ' + result;
}
return result;
}
// Update the file title
var title = localStorage[fileIndex + ".title"];
document.title = "StackEdit - " + title;
$("#file-title").html(composeTitle(fileIndex));
$(".file-title").text(title);
$("#file-title-input").val(title);
// Update the file selector
$("#file-selector").empty();
for ( var i = 0; i < fileDescList.length; i++) {
var fileDesc = fileDescList[i];
var a = $("<a>").html(composeTitle(fileDesc.index));
var li = $("<li>").append(a);
if (fileDesc.index == fileIndex) {
li.addClass("disabled");
} else {
a.prop("href", "#").click((function(fileIndex) {
return function() {
localStorage["file.current"] = fileIndex;
fileManager.selectFile();
};
})(fileDesc.index));
}
$("#file-selector").append(li);
}
};
// Remove a synchronized location
fileManager.removeSync = function(fileSyncIndex) {
var fileIndexList = localStorage["file.list"].split(";");
// Look for local files associated to this synchronized location
for ( var i = 1; i < fileIndexList.length - 1; i++) {
var fileIndexSync = fileIndexList[i] + ".sync";
localStorage[fileIndexSync] = localStorage[fileIndexSync].replace(";"
+ fileSyncIndex + ";", ";");
}
// Remove etag
localStorage.removeItem(fileSyncIndex + ".etag");
};
// Look for local file associated to a synchronized location
fileManager.getFileIndexFromSync = function(fileSyncIndex) {
var fileIndex = undefined;
var fileIndexList = localStorage["file.list"].split(";");
for ( var i = 1; i < fileIndexList.length - 1; i++) {
var tempFileIndex = fileIndexList[i];
var sync = localStorage[tempFileIndex + ".sync"];
if (sync.indexOf(";" + fileSyncIndex + ";") !== -1) {
fileIndex = tempFileIndex;
break;
}
}
return fileIndex;
};
function uploadGdrive() {
$(".file-sync-indicator").removeClass("hide");
var fileIndex = localStorage["file.current"];
var content = localStorage[fileIndex + ".content"];
var title = localStorage[fileIndex + ".title"];
gdrive.createFile(title, content, function(fileSyncIndex) {
if (fileSyncIndex) {
localStorage[fileIndex + ".sync"] += fileSyncIndex + ";";
fileManager.updateFileTitles();
showMessage('The file "' + title
+ '" will now be synchronized on Google Drive.');
} else {
showError("Error while creating file on Google Drive.");
}
});
}
function refreshManageSync() {
var fileIndex = localStorage["file.current"];
var fileSyncIndexList = localStorage[fileIndex + ".sync"].split(";");
$(".msg-no-sync, .msg-sync-list").addClass("hide");
$("#manage-sync-list .input-append").remove();
if (fileSyncIndexList.length > 2) {
$(".msg-sync-list").removeClass("hide");
} else {
$(".msg-no-sync").removeClass("hide");
}
for ( var i = 1; i < fileSyncIndexList.length - 1; i++) {
var fileSyncIndex = fileSyncIndexList[i];
(function(fileSyncIndex) {
var line = $("<div>").addClass("input-append");
if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
line.append($("<input>").prop("type", "text").prop(
"disabled", true).addClass("span5").val(
"Google Drive, FileID="
+ fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length)));
line.append($("<a>").addClass("btn").html(
'<i class="icon-trash"></i>').prop("title",
"Remove this synchronized location").click(function() {
fileManager.removeSync(fileSyncIndex);
fileManager.updateFileTitles();
refreshManageSync();
}));
}
$("#manage-sync-list").append(line);
})(fileSyncIndex);
}
}
return fileManager;
})(jQuery);
var core = (function($) {
var core = {};
core.init = function() {
this.loadSettings();
this.createLayout();
$(".action-load-settings").click(function() {
core.loadSettings();
});
$(".action-apply-settings").click(function() {
core.saveSettings();
fileManager.saveFile();
location.reload();
});
};
var settings = { layoutOrientation : "horizontal" };
core.loadSettings = function() {
if (localStorage.settings) {
$.extend(settings, JSON.parse(localStorage.settings));
}
// Layout orientation
$(
"input:radio[name=radio-layout-orientation][value="
+ settings.layoutOrientation + "]").prop("checked", true);
};
core.saveSettings = function() {
// Layout orientation
settings.layoutOrientation = $(
"input:radio[name=radio-layout-orientation]:checked").prop("value");
localStorage.settings = JSON.stringify(settings);
};
core.createLayout = function() {
var layout = undefined;
var layoutGlobalConfig = { closable : true, resizable : false,
slidable : false, livePaneResizing : true,
enableCursorHotkey : false, spacing_open : 15, spacing_closed : 15,
togglerLength_open : 90, togglerLength_closed : 90,
center__minWidth : 100, center__minHeight : 100,
stateManagement__enabled : false, };
if (settings.layoutOrientation == "horizontal") {
$(".ui-layout-south").remove();
$(".ui-layout-east").addClass("well").prop("id", "wmd-preview");
layout = $('body').layout(
$.extend(layoutGlobalConfig, { east__resizable : true,
east__size : .5, east__minSize : 200 }));
} else if (settings.layoutOrientation == "vertical") {
$(".ui-layout-east").remove();
$(".ui-layout-south").addClass("well").prop("id", "wmd-preview");
layout = $('body').layout(
$.extend(layoutGlobalConfig, { south__resizable : true,
south__size : .5, south__minSize : 200 }));
}
$(".ui-layout-toggler-north").addClass("btn").append(
$("<b>").addClass("caret"));
$(".ui-layout-toggler-south").addClass("btn").append(
$("<b>").addClass("caret"));
$(".ui-layout-toggler-east").addClass("btn").append(
$("<b>").addClass("caret"));
$("#navbar").click(function() {
layout.allowOverflow('north');
});
};
core.createEditor = function(onTextChange) {
$("#wmd-button-bar").empty();
var converter = Markdown.getSanitizingConverter();
var firstChange = true;
converter.hooks.chain("preConversion", function(text) {
if (!firstChange) {
onTextChange();
}
return text;
});
var editor = new Markdown.Editor(converter);
editor.run();
firstChange = false;
$(".wmd-button-row").addClass("btn-group").find("li:not(.wmd-spacer)")
.addClass("btn").css("left", 0).find("span").hide();
$("#wmd-bold-button").append($("<i>").addClass("icon-bold"));
$("#wmd-italic-button").append($("<i>").addClass("icon-italic"));
$("#wmd-link-button").append($("<i>").addClass("icon-globe"));
$("#wmd-quote-button").append($("<i>").addClass("icon-indent-left"));
$("#wmd-code-button").append($("<i>").addClass("icon-code"));
$("#wmd-image-button").append($("<i>").addClass("icon-picture"));
$("#wmd-olist-button").append($("<i>").addClass("icon-numbered-list"));
$("#wmd-ulist-button").append($("<i>").addClass("icon-list"));
$("#wmd-heading-button").append($("<i>").addClass("icon-text-height"));
$("#wmd-hr-button").append($("<i>").addClass("icon-hr"));
$("#wmd-undo-button").append($("<i>").addClass("icon-undo"));
$("#wmd-redo-button").append($("<i>").addClass("icon-share-alt"));
};
return core;
})(jQuery);
(function($) {
$(function() { $(function() {
// jGrowl configuration
$.jGrowl.defaults.life = 5000;
$.jGrowl.defaults.closer = false;
$.jGrowl.defaults.closeTemplate = '';
$.jGrowl.defaults.position = 'bottom-right';
core.init(); core.init();
// listen to online/offline events
$(window).on('offline', onOffline);
$(window).on('online', onOnline);
if (navigator.onLine === false) {
onOffline();
}
fileManager.init(); fileManager.init();
}); });
});
})(jQuery);

1
js/prod/custo.js Normal file
View File

@ -0,0 +1 @@
var GOOGLE_CLIENT_ID = '241271498917-jpto9lls9fqnem1e4h6ppds9uob8rpvu.apps.googleusercontent.com';

2019
js/require.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,24 @@
var SYNC_PERIOD = 60000; define(["jquery", "core", "gdrive"], function($, core, gdrive) {
var SYNC_PROVIDER_GDRIVE = "sync.gdrive.";
var syncGoogleDrive = false;
var synchronizer = (function() {
var synchronizer = {}; var synchronizer = {};
// Dependencies
var fileManager = undefined;
// Used to know the providers we are connected to
synchronizer.useGoogleDrive = false;
var onSyncBegin = undefined; var onSyncBegin = undefined;
var onSyncEnd = undefined; var onSyncEnd = undefined;
var onQueueChanged = undefined; var onQueueChanged = undefined;
var doNothing = function() {
};
// A synchronization queue containing fileIndex that has to be synchronized // A synchronization queue containing fileIndex that has to be synchronized
var syncUpQueue = undefined; var syncUpQueue = undefined;
synchronizer.init = function(options) { synchronizer.init = function(fileManagerModule, options) {
onSyncBegin = options.onSyncBegin || doNothing; fileManager = fileManagerModule;
onSyncEnd = options.onSyncEnd || doNothing; onSyncBegin = options.onSyncBegin || core.doNothing;
onQueueChanged = options.onQueueChanged || doNothing; onSyncEnd = options.onSyncEnd || core.doNothing;
onQueueChanged = options.onQueueChanged || core.doNothing;
syncUpQueue = ";"; syncUpQueue = ";";
// Load the queue from localStorage in case a previous synchronization // Load the queue from localStorage in case a previous synchronization
@ -61,15 +61,15 @@ var synchronizer = (function() {
if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) { if (fileSyncIndex.indexOf(SYNC_PROVIDER_GDRIVE) === 0) {
var id = fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length); var id = fileSyncIndex.substring(SYNC_PROVIDER_GDRIVE.length);
gdrive.updateFile(id, title, content, function(result) { gdrive.updateFile(id, title, content, function(result) {
if (result === undefined && offline === true) { if (result !== undefined) {
// If we detect offline mode we put the fileIndex back in fileUp(fileSyncIndexList, content, title, callback);
// the queue
synchronizer.addFileForUpload(localStorage["sync.current"]);
localStorage.removeItem("sync.current");
callback();
return; return;
} }
fileUp(fileSyncIndexList, content, title, callback); // If error we put the fileIndex back in the queue
synchronizer.addFileForUpload(localStorage["sync.current"]);
localStorage.removeItem("sync.current");
callback();
return;
}); });
} else { } else {
fileUp(fileSyncIndexList, content, title, callback); fileUp(fileSyncIndexList, content, title, callback);
@ -100,7 +100,7 @@ var synchronizer = (function() {
}; };
function syncDownGdrive(callback) { function syncDownGdrive(callback) {
if (syncGoogleDrive === false) { if (synchronizer.useGoogleDrive === false) {
callback(); callback();
return; return;
} }
@ -132,7 +132,7 @@ var synchronizer = (function() {
if (change.deleted === true) { if (change.deleted === true) {
fileManager.removeSync(fileSyncIndex); fileManager.removeSync(fileSyncIndex);
updateFileTitles = true; updateFileTitles = true;
showMessage('"' + title + '" has been removed from Google Drive.'); core.showMessage('"' + title + '" has been removed from Google Drive.');
continue; continue;
} }
var content = localStorage[fileIndex + ".content"]; var content = localStorage[fileIndex + ".content"];
@ -143,18 +143,18 @@ var synchronizer = (function() {
if ((titleChanged || contentChanged) && syncUpQueue.indexOf(";" + fileIndex + ";") !== -1) { if ((titleChanged || contentChanged) && syncUpQueue.indexOf(";" + fileIndex + ";") !== -1) {
fileManager.createFile(title + " (backup)", content); fileManager.createFile(title + " (backup)", content);
updateFileTitles = true; updateFileTitles = true;
showMessage('Conflict detected on "' + title + '". A backup has been created locally.'); core.showMessage('Conflict detected on "' + title + '". A backup has been created locally.');
} }
// If file title changed // If file title changed
if(titleChanged) { if(titleChanged) {
localStorage[fileIndex + ".title"] = file.title; localStorage[fileIndex + ".title"] = file.title;
updateFileTitles = true; updateFileTitles = true;
showMessage('"' + title + '" has been renamed to "' + file.title + '" on Google Drive.'); core.showMessage('"' + title + '" has been renamed to "' + file.title + '" on Google Drive.');
} }
// If file content changed // If file content changed
if(contentChanged) { if(contentChanged) {
localStorage[fileIndex + ".content"] = file.content; localStorage[fileIndex + ".content"] = file.content;
showMessage('"' + file.title + '" has been updated from Google Drive.'); core.showMessage('"' + file.title + '" has been updated from Google Drive.');
if(fileIndex == localStorage["file.current"]) { if(fileIndex == localStorage["file.current"]) {
updateFileTitles = false; // Done by next function updateFileTitles = false; // Done by next function
fileManager.selectFile(); fileManager.selectFile();
@ -183,11 +183,11 @@ var synchronizer = (function() {
var lastSync = 0; var lastSync = 0;
synchronizer.sync = function() { synchronizer.sync = function() {
// If sync is already running or timeout is not reached or offline // If sync is already running or timeout is not reached or offline
if (syncRunning || lastSync + SYNC_PERIOD > currentTime || offline) { if (syncRunning || lastSync + SYNC_PERIOD > core.currentTime || core.isOffline) {
return; return;
} }
syncRunning = true; syncRunning = true;
lastSync = currentTime; lastSync = core.currentTime;
onSyncBegin(); onSyncBegin();
syncDown(function() { syncDown(function() {
@ -212,4 +212,4 @@ var synchronizer = (function() {
}; };
return synchronizer; return synchronizer;
})(); });