├── README.mkd ├── lightwrite ├── .local_settings.py.swp ├── .urls.py.swp ├── __init__.py ├── local_settings.py ├── manage.py ├── run.sh ├── settings.py ├── templates │ ├── .light.html.swp │ ├── about.html │ └── light.html ├── texts │ ├── __init__.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py └── urls.py └── static ├── clippy.swf ├── favicon.ico ├── favikon.ico ├── gunio.png ├── gunio2.png ├── jquery.textarea.js ├── light.css ├── smoke.css ├── smoke.min.js └── themes └── dark.css /README.mkd: -------------------------------------------------------------------------------- 1 | # [LightWrite](http://gun.io/w/) - by Gun.io 2 | ## A web-based WriteRoom clone 3 | ### Rich Jones - rich@gun.io 4 | 5 | LightWrite is a free and open source web-based clone of [WriteRoom](http://www.hogbaysoftware.com/products/writeroom) by HogBaySoftware. 6 | 7 | LightWrite is built on lots of open source componants, including Python, Django, jQuery, and Smoke.js. 8 | 9 | Are there bugs or features you want for LightWrite? Get in touch! 10 | -------------------------------------------------------------------------------- /lightwrite/.local_settings.py.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miserlou/LightWrite/2f76420a05d297b77f812300b83488fa3a39e9e2/lightwrite/.local_settings.py.swp -------------------------------------------------------------------------------- /lightwrite/.urls.py.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miserlou/LightWrite/2f76420a05d297b77f812300b83488fa3a39e9e2/lightwrite/.urls.py.swp -------------------------------------------------------------------------------- /lightwrite/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miserlou/LightWrite/2f76420a05d297b77f812300b83488fa3a39e9e2/lightwrite/__init__.py -------------------------------------------------------------------------------- /lightwrite/local_settings.py: -------------------------------------------------------------------------------- 1 | MEDIA_ROOT = '/home/tuttle/Projects/LightWrite/static' 2 | SITE_ROOT = '/home/tuttle/Projects/LightWrite/' 3 | STATICFILES_DIRS = ( 4 | '/home/tuttle/Projects/LightWrite/static/', 5 | ) 6 | 7 | -------------------------------------------------------------------------------- /lightwrite/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | import imp 4 | try: 5 | imp.find_module('settings') # Assumed to be in the same directory. 6 | except ImportError: 7 | import sys 8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) 9 | sys.exit(1) 10 | 11 | import settings 12 | 13 | if __name__ == "__main__": 14 | execute_manager(settings) 15 | -------------------------------------------------------------------------------- /lightwrite/run.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | python manage.py runserver 3 | -------------------------------------------------------------------------------- /lightwrite/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Django settings for lightwrite project. 4 | 5 | DEBUG = True 6 | TEMPLATE_DEBUG = DEBUG 7 | 8 | ADMINS = ( 9 | # ('Your Name', 'your_email@example.com'), 10 | ) 11 | 12 | MANAGERS = ADMINS 13 | 14 | DATABASES = { 15 | 'default': { 16 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 17 | 'NAME': 'text_db', # Or path to database file if using sqlite3. 18 | 'USER': '', # Not used with sqlite3. 19 | 'PASSWORD': '', # Not used with sqlite3. 20 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 21 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 22 | } 23 | } 24 | 25 | # Local time zone for this installation. Choices can be found here: 26 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 27 | # although not all choices may be available on all operating systems. 28 | # On Unix systems, a value of None will cause Django to use the same 29 | # timezone as the operating system. 30 | # If running in a Windows environment this must be set to the same as your 31 | # system time zone. 32 | TIME_ZONE = 'America/Chicago' 33 | 34 | # Language code for this installation. All choices can be found here: 35 | # http://www.i18nguy.com/unicode/language-identifiers.html 36 | LANGUAGE_CODE = 'en-us' 37 | 38 | SITE_ID = 1 39 | 40 | # If you set this to False, Django will make some optimizations so as not 41 | # to load the internationalization machinery. 42 | USE_I18N = True 43 | 44 | # If you set this to False, Django will not format dates, numbers and 45 | # calendars according to the current locale 46 | USE_L10N = True 47 | 48 | # Absolute filesystem path to the directory that will hold user-uploaded files. 49 | # Example: "/home/media/media.lawrence.com/media/" 50 | MEDIA_ROOT = '' 51 | 52 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 53 | # trailing slash. 54 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 55 | MEDIA_URL = '' 56 | 57 | # Absolute path to the directory static files should be collected to. 58 | # Don't put anything in this directory yourself; store your static files 59 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 60 | # Example: "/home/media/media.lawrence.com/static/" 61 | STATIC_ROOT = '' 62 | 63 | # URL prefix for static files. 64 | # Example: "http://media.lawrence.com/static/" 65 | STATIC_URL = '/static/' 66 | 67 | # URL prefix for admin static files -- CSS, JavaScript and images. 68 | # Make sure to use a trailing slash. 69 | # Examples: "http://foo.com/static/admin/", "/static/admin/". 70 | ADMIN_MEDIA_PREFIX = '/static/admin/' 71 | 72 | # Additional locations of static files 73 | STATICFILES_DIRS = ( 74 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 75 | # Always use forward slashes, even on Windows. 76 | # Don't forget to use absolute paths, not relative paths. 77 | ) 78 | 79 | # List of finder classes that know how to find static files in 80 | # various locations. 81 | STATICFILES_FINDERS = ( 82 | 'django.contrib.staticfiles.finders.FileSystemFinder', 83 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 84 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 85 | ) 86 | 87 | # Make this unique, and don't share it with anybody. 88 | SECRET_KEY = '4!&@ud38$05-t!-8(7nr4u2*tn9p!7yme%wy#lx%fqrgn44=v5' 89 | 90 | # List of callables that know how to import templates from various sources. 91 | TEMPLATE_LOADERS = ( 92 | 'django.template.loaders.filesystem.Loader', 93 | 'django.template.loaders.app_directories.Loader', 94 | # 'django.template.loaders.eggs.Loader', 95 | ) 96 | 97 | TEMPLATE_DIRS = ( 98 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 99 | # Always use forward slashes, even on Windows. 100 | # Don't forget to use absolute paths, not relative paths. 101 | os.path.join(os.path.dirname(__file__), 'templates'), 102 | ) 103 | 104 | 105 | MIDDLEWARE_CLASSES = ( 106 | 'django.middleware.common.CommonMiddleware', 107 | 'django.contrib.sessions.middleware.SessionMiddleware', 108 | 'django.middleware.csrf.CsrfViewMiddleware', 109 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 110 | 'django.contrib.messages.middleware.MessageMiddleware', 111 | # Uncomment the next line for simple clickjacking protection: 112 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware', 113 | ) 114 | 115 | ROOT_URLCONF = 'lightwrite.urls' 116 | 117 | INSTALLED_APPS = ( 118 | 'django.contrib.auth', 119 | 'django.contrib.contenttypes', 120 | 'django.contrib.sessions', 121 | 'django.contrib.sites', 122 | 'django.contrib.messages', 123 | 'django.contrib.staticfiles', 124 | 'texts' 125 | # Uncomment the next line to enable the admin: 126 | # 'django.contrib.admin', 127 | # Uncomment the next line to enable admin documentation: 128 | # 'django.contrib.admindocs', 129 | ) 130 | 131 | try: 132 | from local_settings import * 133 | except: 134 | pass 135 | 136 | -------------------------------------------------------------------------------- /lightwrite/templates/.light.html.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miserlou/LightWrite/2f76420a05d297b77f812300b83488fa3a39e9e2/lightwrite/templates/.light.html.swp -------------------------------------------------------------------------------- /lightwrite/templates/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | About LightWrite 4 | 5 |

About LightWrite

6 |

LightWrite is a free and open source web-based clone of WriteRoom by HogBaySoftware. 7 |

The source code is available on GitHub here. 8 |

LightWrite is built on lots of open source componants, including Python, Django, jQuery, and Smoke.js. 9 |

Are there bugs or features you want for LightWrite? Get in touch! 10 | 11 |

About Gun.io

12 |

LightWrite is written in Django by Rich Jones for Gun.io, a place for independent and open source developers to hire assistance on their projects. 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /lightwrite/templates/light.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | LightWrite - Gun.io 4 | 5 | 6 | 7 | 24 | 39 | 44 | 68 | 81 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |

102 |
103 |
104 |
{% csrf_token %} 105 |
106 |
107 | 123 |
124 | 125 | 126 | -------------------------------------------------------------------------------- /lightwrite/texts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miserlou/LightWrite/2f76420a05d297b77f812300b83488fa3a39e9e2/lightwrite/texts/__init__.py -------------------------------------------------------------------------------- /lightwrite/texts/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django import forms 3 | from django.forms import ModelForm 4 | 5 | # Create your models here. 6 | 7 | class Text(models.Model): 8 | wash = models.CharField(max_length=12, blank=False) 9 | locked = models.BooleanField(default=False) 10 | text = models.TextField(blank=False) 11 | 12 | class TextForm(forms.ModelForm): 13 | class Meta: 14 | model = Text 15 | 16 | def save(self): 17 | if not self.instance: 18 | self.bound_object = Text() 19 | 20 | self.bound_object.text = self.cleaned_data['text'] 21 | self.bound_object.wash = self.cleaned_data['wash'] 22 | self.bound_object.save() 23 | 24 | -------------------------------------------------------------------------------- /lightwrite/texts/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /lightwrite/texts/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('', 4 | ) 5 | 6 | -------------------------------------------------------------------------------- /lightwrite/texts/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse, HttpResponseRedirect 2 | from django.template import Context, loader 3 | from django.core.urlresolvers import reverse 4 | from django.template import RequestContext 5 | from django.shortcuts import get_object_or_404, render_to_response 6 | from django.core import serializers 7 | from django.conf import settings 8 | 9 | from lightwrite.texts.models import Text, TextForm 10 | 11 | import json 12 | import random 13 | import string 14 | 15 | # Redirect to a random page 16 | # If deploying, make sure you have a REDIR in your local_settings 17 | # TODO: Don't redirect to already taken pads 18 | def root(request): 19 | wash = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(9)) 20 | return HttpResponseRedirect(settings.REDIR + '/' + wash) 21 | 22 | # The main event 23 | def write(request, wash): 24 | if request.method == 'POST': 25 | t = Text.objects.filter(wash=wash) 26 | if (len(t)==0): 27 | t = Text(wash=wash) 28 | t.text = request.POST['writearea'] 29 | t.save() 30 | t = Text.objects.filter(wash=wash) 31 | else: 32 | t = t[0] 33 | t.text = request.POST['writearea'] 34 | t.save() 35 | t = Text.objects.filter(wash=wash) 36 | if 'save' in request.POST: 37 | return render_to_response('light.html', {'wash': wash,}, context_instance=RequestContext(request)) 38 | else: 39 | resp = HttpResponse(t[0].text.strip(), mimetype='text/plain') 40 | resp['Content-Disposition'] = 'attachment; filename=' + wash + '.txt' 41 | return resp 42 | 43 | return render_to_response('light.html', {'wash': wash,}, context_instance=RequestContext(request)) 44 | 45 | # Get the text result 46 | def json_get_text(request, wash): 47 | t = Text.objects.filter(wash=wash) 48 | if (len(t)==0): 49 | t = Text(wash=wash) 50 | # Display mac command as HTML entity 51 | t.text = '\n\nWelcome to LightWrite!\nby Gun.io\n\nLightWrite is a web-based clone of WriteRoom - it\'s a place to write your thoughts without any distractions.\n\nTo use LightWrite, just start typing! You can move your mouse over the bottom of the page to save your text online or export to a file. \n\nTo enter fullscreen mode, just press F11, or ⌘F if you\'re using OSX.\n\nEnjoy!\n\nLove,\nTeam Gun.io' 52 | t.save() 53 | t = Text.objects.filter(wash=wash) 54 | data = serializers.serialize("json", t) 55 | return HttpResponse(data, mimetype='application/json') 56 | 57 | def about(request): 58 | return render_to_response('about.html') 59 | -------------------------------------------------------------------------------- /lightwrite/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, include, url 2 | from django.conf import settings 3 | 4 | # Uncomment the next two lines to enable the admin: 5 | # from django.contrib import admin 6 | # admin.autodiscover() 7 | 8 | urlpatterns = patterns('', 9 | # Examples: 10 | # url(r'^$', 'lightwrite.views.home', name='home'), 11 | url(r'^(?P[a-zA-Z0-9_.-]+)$', 'lightwrite.texts.views.write'), 12 | url(r'^t/(?P[a-zA-Z0-9_.-]+)/$', 'lightwrite.texts.views.json_get_text'), 13 | url(r'^a/about/$', 'lightwrite.texts.views.about'), 14 | url(r'^static/(?P.*)$', 'django.views.static.serve', {'document_root':settings.MEDIA_ROOT}), 15 | url(r'^$', 'lightwrite.texts.views.root'), 16 | # Uncomment the admin/doc line below to enable admin documentation: 17 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 18 | 19 | # Uncomment the next line to enable the admin: 20 | # url(r'^admin/', include(admin.site.urls)), 21 | ) 22 | 23 | -------------------------------------------------------------------------------- /static/clippy.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miserlou/LightWrite/2f76420a05d297b77f812300b83488fa3a39e9e2/static/clippy.swf -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miserlou/LightWrite/2f76420a05d297b77f812300b83488fa3a39e9e2/static/favicon.ico -------------------------------------------------------------------------------- /static/favikon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miserlou/LightWrite/2f76420a05d297b77f812300b83488fa3a39e9e2/static/favikon.ico -------------------------------------------------------------------------------- /static/gunio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miserlou/LightWrite/2f76420a05d297b77f812300b83488fa3a39e9e2/static/gunio.png -------------------------------------------------------------------------------- /static/gunio2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miserlou/LightWrite/2f76420a05d297b77f812300b83488fa3a39e9e2/static/gunio2.png -------------------------------------------------------------------------------- /static/jquery.textarea.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Tabby jQuery plugin version 0.12 3 | * 4 | * Ted Devito - http://teddevito.com/demos/textarea.html 5 | * 6 | * Copyright (c) 2009 Ted Devito 7 | * 8 | * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 9 | * conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 13 | * in the documentation and/or other materials provided with the distribution. 14 | * 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written 15 | * permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 22 | * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | * 24 | */ 25 | 26 | // create closure 27 | 28 | (function($) { 29 | 30 | // plugin definition 31 | 32 | $.fn.tabby = function(options) { 33 | //debug(this); 34 | // build main options before element iteration 35 | var opts = $.extend({}, $.fn.tabby.defaults, options); 36 | var pressed = $.fn.tabby.pressed; 37 | 38 | // iterate and reformat each matched element 39 | return this.each(function() { 40 | $this = $(this); 41 | 42 | // build element specific options 43 | var options = $.meta ? $.extend({}, opts, $this.data()) : opts; 44 | 45 | $this.bind('keydown',function (e) { 46 | var kc = $.fn.tabby.catch_kc(e); 47 | if (16 == kc) pressed.shft = true; 48 | /* 49 | because both CTRL+TAB and ALT+TAB default to an event (changing tab/window) that 50 | will prevent js from capturing the keyup event, we'll set a timer on releasing them. 51 | */ 52 | if (17 == kc) {pressed.ctrl = true; setTimeout("$.fn.tabby.pressed.ctrl = false;",1000);} 53 | if (18 == kc) {pressed.alt = true; setTimeout("$.fn.tabby.pressed.alt = false;",1000);} 54 | 55 | if (9 == kc && !pressed.ctrl && !pressed.alt) { 56 | e.preventDefault; // does not work in O9.63 ?? 57 | pressed.last = kc; setTimeout("$.fn.tabby.pressed.last = null;",0); 58 | process_keypress ($(e.target).get(0), pressed.shft, options); 59 | return false; 60 | } 61 | 62 | }).bind('keyup',function (e) { 63 | if (16 == $.fn.tabby.catch_kc(e)) pressed.shft = false; 64 | }).bind('blur',function (e) { // workaround for Opera -- http://www.webdeveloper.com/forum/showthread.php?p=806588 65 | if (9 == pressed.last) $(e.target).one('focus',function (e) {pressed.last = null;}).get(0).focus(); 66 | }); 67 | 68 | }); 69 | }; 70 | 71 | // define and expose any extra methods 72 | $.fn.tabby.catch_kc = function(e) { return e.keyCode ? e.keyCode : e.charCode ? e.charCode : e.which; }; 73 | $.fn.tabby.pressed = {shft : false, ctrl : false, alt : false, last: null}; 74 | 75 | // private function for debugging 76 | function debug($obj) { 77 | if (window.console && window.console.log) 78 | window.console.log('textarea count: ' + $obj.size()); 79 | }; 80 | 81 | function process_keypress (o,shft,options) { 82 | var scrollTo = o.scrollTop; 83 | //var tabString = String.fromCharCode(9); 84 | 85 | // gecko; o.setSelectionRange is only available when the text box has focus 86 | if (o.setSelectionRange) gecko_tab (o, shft, options); 87 | 88 | // ie; document.selection is always available 89 | else if (document.selection) ie_tab (o, shft, options); 90 | 91 | o.scrollTop = scrollTo; 92 | } 93 | 94 | // plugin defaults 95 | $.fn.tabby.defaults = {tabString : String.fromCharCode(9)}; 96 | 97 | function gecko_tab (o, shft, options) { 98 | var ss = o.selectionStart; 99 | var es = o.selectionEnd; 100 | 101 | // when there's no selection and we're just working with the caret, we'll add/remove the tabs at the caret, providing more control 102 | if(ss == es) { 103 | // SHIFT+TAB 104 | if (shft) { 105 | // check to the left of the caret first 106 | if ("\t" == o.value.substring(ss-options.tabString.length, ss)) { 107 | o.value = o.value.substring(0, ss-options.tabString.length) + o.value.substring(ss); // put it back together omitting one character to the left 108 | o.focus(); 109 | o.setSelectionRange(ss - options.tabString.length, ss - options.tabString.length); 110 | } 111 | // then check to the right of the caret 112 | else if ("\t" == o.value.substring(ss, ss + options.tabString.length)) { 113 | o.value = o.value.substring(0, ss) + o.value.substring(ss + options.tabString.length); // put it back together omitting one character to the right 114 | o.focus(); 115 | o.setSelectionRange(ss,ss); 116 | } 117 | } 118 | // TAB 119 | else { 120 | o.value = o.value.substring(0, ss) + options.tabString + o.value.substring(ss); 121 | o.focus(); 122 | o.setSelectionRange(ss + options.tabString.length, ss + options.tabString.length); 123 | } 124 | } 125 | // selections will always add/remove tabs from the start of the line 126 | else { 127 | // split the textarea up into lines and figure out which lines are included in the selection 128 | var lines = o.value.split("\n"); 129 | var indices = new Array(); 130 | var sl = 0; // start of the line 131 | var el = 0; // end of the line 132 | var sel = false; 133 | for (var i in lines) { 134 | el = sl + lines[i].length; 135 | indices.push({start: sl, end: el, selected: (sl <= ss && el > ss) || (el >= es && sl < es) || (sl > ss && el < es)}); 136 | sl = el + 1;// for "\n" 137 | } 138 | 139 | // walk through the array of lines (indices) and add tabs where appropriate 140 | var modifier = 0; 141 | for (var i in indices) { 142 | if (indices[i].selected) { 143 | var pos = indices[i].start + modifier; // adjust for tabs already inserted/removed 144 | // SHIFT+TAB 145 | if (shft && options.tabString == o.value.substring(pos,pos+options.tabString.length)) { // only SHIFT+TAB if there's a tab at the start of the line 146 | o.value = o.value.substring(0,pos) + o.value.substring(pos + options.tabString.length); // omit the tabstring to the right 147 | modifier -= options.tabString.length; 148 | } 149 | // TAB 150 | else if (!shft) { 151 | o.value = o.value.substring(0,pos) + options.tabString + o.value.substring(pos); // insert the tabstring 152 | modifier += options.tabString.length; 153 | } 154 | } 155 | } 156 | o.focus(); 157 | var ns = ss + ((modifier > 0) ? options.tabString.length : (modifier < 0) ? -options.tabString.length : 0); 158 | var ne = es + modifier; 159 | o.setSelectionRange(ns,ne); 160 | } 161 | } 162 | 163 | function ie_tab (o, shft, options) { 164 | var range = document.selection.createRange(); 165 | 166 | if (o == range.parentElement()) { 167 | // when there's no selection and we're just working with the caret, we'll add/remove the tabs at the caret, providing more control 168 | if ('' == range.text) { 169 | // SHIFT+TAB 170 | if (shft) { 171 | var bookmark = range.getBookmark(); 172 | //first try to the left by moving opening up our empty range to the left 173 | range.moveStart('character', -options.tabString.length); 174 | if (options.tabString == range.text) { 175 | range.text = ''; 176 | } else { 177 | // if that didn't work then reset the range and try opening it to the right 178 | range.moveToBookmark(bookmark); 179 | range.moveEnd('character', options.tabString.length); 180 | if (options.tabString == range.text) 181 | range.text = ''; 182 | } 183 | // move the pointer to the start of them empty range and select it 184 | range.collapse(true); 185 | range.select(); 186 | } 187 | 188 | else { 189 | // very simple here. just insert the tab into the range and put the pointer at the end 190 | range.text = options.tabString; 191 | range.collapse(false); 192 | range.select(); 193 | } 194 | } 195 | // selections will always add/remove tabs from the start of the line 196 | else { 197 | 198 | var selection_text = range.text; 199 | var selection_len = selection_text.length; 200 | var selection_arr = selection_text.split("\r\n"); 201 | 202 | var before_range = document.body.createTextRange(); 203 | before_range.moveToElementText(o); 204 | before_range.setEndPoint("EndToStart", range); 205 | var before_text = before_range.text; 206 | var before_arr = before_text.split("\r\n"); 207 | var before_len = before_text.length; // - before_arr.length + 1; 208 | 209 | var after_range = document.body.createTextRange(); 210 | after_range.moveToElementText(o); 211 | after_range.setEndPoint("StartToEnd", range); 212 | var after_text = after_range.text; // we can accurately calculate distance to the end because we're not worried about MSIE trimming a \r\n 213 | 214 | var end_range = document.body.createTextRange(); 215 | end_range.moveToElementText(o); 216 | end_range.setEndPoint("StartToEnd", before_range); 217 | var end_text = end_range.text; // we can accurately calculate distance to the end because we're not worried about MSIE trimming a \r\n 218 | 219 | var check_html = $(o).html(); 220 | $("#r3").text(before_len + " + " + selection_len + " + " + after_text.length + " = " + check_html.length); 221 | if((before_len + end_text.length) < check_html.length) { 222 | before_arr.push(""); 223 | before_len += 2; // for the \r\n that was trimmed 224 | if (shft && options.tabString == selection_arr[0].substring(0,options.tabString.length)) 225 | selection_arr[0] = selection_arr[0].substring(options.tabString.length); 226 | else if (!shft) selection_arr[0] = options.tabString + selection_arr[0]; 227 | } else { 228 | if (shft && options.tabString == before_arr[before_arr.length-1].substring(0,options.tabString.length)) 229 | before_arr[before_arr.length-1] = before_arr[before_arr.length-1].substring(options.tabString.length); 230 | else if (!shft) before_arr[before_arr.length-1] = options.tabString + before_arr[before_arr.length-1]; 231 | } 232 | 233 | for (var i = 1; i < selection_arr.length; i++) { 234 | if (shft && options.tabString == selection_arr[i].substring(0,options.tabString.length)) 235 | selection_arr[i] = selection_arr[i].substring(options.tabString.length); 236 | else if (!shft) selection_arr[i] = options.tabString + selection_arr[i]; 237 | } 238 | 239 | if (1 == before_arr.length && 0 == before_len) { 240 | if (shft && options.tabString == selection_arr[0].substring(0,options.tabString.length)) 241 | selection_arr[0] = selection_arr[0].substring(options.tabString.length); 242 | else if (!shft) selection_arr[0] = options.tabString + selection_arr[0]; 243 | } 244 | 245 | if ((before_len + selection_len + after_text.length) < check_html.length) { 246 | selection_arr.push(""); 247 | selection_len += 2; // for the \r\n that was trimmed 248 | } 249 | 250 | before_range.text = before_arr.join("\r\n"); 251 | range.text = selection_arr.join("\r\n"); 252 | 253 | var new_range = document.body.createTextRange(); 254 | new_range.moveToElementText(o); 255 | 256 | if (0 < before_len) new_range.setEndPoint("StartToEnd", before_range); 257 | else new_range.setEndPoint("StartToStart", before_range); 258 | new_range.setEndPoint("EndToEnd", range); 259 | 260 | new_range.select(); 261 | 262 | } 263 | } 264 | } 265 | 266 | // end of closure 267 | })(jQuery); 268 | -------------------------------------------------------------------------------- /static/light.css: -------------------------------------------------------------------------------- 1 | html{ 2 | padding: 0; 3 | margin: 0; 4 | overflow-x: hidden; 5 | overflow-y: hidden; 6 | 7 | } 8 | 9 | a{border-width: 0px;} 10 | img{border-width: 0px;} 11 | 12 | body{ 13 | background: #ffffff; 14 | padding: 0; 15 | } 16 | 17 | #main { 18 | margin: 0; 19 | padding: 0; 20 | } 21 | 22 | #main form div textarea#writearea{ 23 | font: 26px/120% 'Georgia',Georgia,serif; 24 | color: #000000; 25 | background: #ffffff; 26 | border: 0; 27 | outline: none; 28 | width: 720px; 29 | height: 80%; 30 | overflow-x: hidden; 31 | overflow-y: auto; 32 | border-spacing: 0; 33 | margin: 0; 34 | padding: 0; 35 | text-shadow: 0 0 1px #666677; 36 | resize: none; 37 | } 38 | 39 | ::-webkit-scrollbar { 40 | width: 1px; 41 | height: 1px; 42 | } 43 | 44 | #footer{ 45 | background: #ffffff; 46 | margin-top: 50px; 47 | height: 5%; 48 | } 49 | 50 | #footer-items{ 51 | background: #ffffff; 52 | height: 50px; 53 | color: #ff0000; 54 | } 55 | 56 | /* clean gray 57 | *******************************************************************************/ 58 | button.clean-gray { 59 | background-color: #eeeeee; 60 | background-image: -webkit-gradient(linear, left top, left bottom, from(#eeeeee), to(#cccccc)); 61 | /* Saf4+, Chrome */ 62 | background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc); 63 | background-image: -moz-linear-gradient(top, #eeeeee, #cccccc); 64 | background-image: -ms-linear-gradient(top, #eeeeee, #cccccc); 65 | background-image: -o-linear-gradient(top, #eeeeee, #cccccc); 66 | background-image: linear-gradient(top, #eeeeee, #cccccc); 67 | border: 1px solid #ccc; 68 | border-bottom: 1px solid #bbb; 69 | -webkit-border-radius: 3px; 70 | -moz-border-radius: 3px; 71 | -ms-border-radius: 3px; 72 | -o-border-radius: 3px; 73 | border-radius: 3px; 74 | color: #333; 75 | font: bold 11px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; 76 | line-height: 1; 77 | padding: 8px 0; 78 | text-align: center; 79 | text-shadow: 0 1px 0 #eee; 80 | width: 100px; 81 | margin-left: 5px; 82 | margin-right: 5px; 83 | } 84 | button.clean-gray:hover { 85 | background-color: #dddddd; 86 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dddddd), to(#bbbbbb)); 87 | /* Saf4+, Chrome */ 88 | background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb); 89 | background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb); 90 | background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb); 91 | background-image: -o-linear-gradient(top, #dddddd, #bbbbbb); 92 | background-image: linear-gradient(top, #dddddd, #bbbbbb); 93 | border: 1px solid #bbb; 94 | border-bottom: 1px solid #999; 95 | cursor: pointer; 96 | text-shadow: 0 1px 0 #ddd; } 97 | button.clean-gray:active { 98 | border: 1px solid #aaa; 99 | border-bottom: 1px solid #888; 100 | -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 101 | -moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 102 | -ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 103 | -o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 104 | box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; } 105 | 106 | /* cupid green (inspired by okcupid.com) 107 | *******************************************************************************/ 108 | button.cupid-green 109 | -------------------------------------------------------------------------------- /static/smoke.css: -------------------------------------------------------------------------------- 1 | .smoke-base { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | bottom: 0; 6 | right: 0; 7 | visibility: hidden; 8 | opacity: 0; 9 | -moz-transition: all .3s; 10 | -webkit-transition: opacity .3s; 11 | -o-transition: all .3s; 12 | transition: all .3s; 13 | } 14 | 15 | .smoke-base.smoke-visible { 16 | opacity: 1; 17 | visibility: visible; 18 | } 19 | 20 | .smokebg { 21 | position: fixed; 22 | top: 0; 23 | left: 0; 24 | bottom: 0; 25 | right: 0; 26 | } 27 | 28 | .smoke-base .dialog { 29 | position: absolute 30 | } 31 | 32 | .dialog-prompt { 33 | margin-top: 5px; 34 | text-align: center; 35 | } 36 | 37 | .dialog-buttons { 38 | margin: 10px 0 5px 0 39 | } 40 | 41 | .smoke { 42 | font-family: sans-serif; 43 | font-weight: bold; 44 | text-align: center; 45 | font-size: 30px; 46 | line-height: 130%; 47 | } 48 | 49 | .dialog-buttons button { 50 | display: inline-block; 51 | vertical-align: baseline; 52 | cursor: pointer; 53 | font-family: sans-serif; 54 | font-style: normal; 55 | text-decoration: none; 56 | border: 0; 57 | outline: 0; 58 | margin: 0 5px; 59 | -webkit-background-clip: padding-box; 60 | font-size: 13px; 61 | line-height: 13px; 62 | font-weight: bold; 63 | padding: 9px 12px; 64 | } 65 | 66 | .dialog-prompt input { 67 | margin: 0; 68 | border: 0; 69 | font-family: sans-serif; 70 | outline: none; 71 | border: 1px solid #333; 72 | width: 97%; 73 | background-color: #fff; 74 | font-size: 15px; 75 | padding: 5px; 76 | } 77 | 78 | .smoke-base { 79 | background: rgba(0,0,0,.3); 80 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#90000000,endColorstr=#900000000); 81 | } 82 | 83 | .smoke-base .dialog { 84 | top: 25%; 85 | left: 25%; 86 | width: 50%; 87 | } 88 | 89 | .smoke-base .dialog-inner { 90 | padding: 15px 91 | } 92 | 93 | .smoke { 94 | text-transform: uppercase; 95 | background-color: rgba(255,255,255,1); 96 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#ffffff); 97 | } 98 | 99 | .dialog-buttons button { 100 | border-radius: 5px; 101 | text-transform: uppercase; 102 | background-color: rgba(0,0,0,.9); 103 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#222222,endColorstr=#222222); 104 | color: #fff; 105 | } 106 | 107 | button.cancel { 108 | background-color: rgba(0,0,0,.7); 109 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#444444,endColorstr=#444444); 110 | } 111 | 112 | .queue{ 113 | display:none; 114 | } -------------------------------------------------------------------------------- /static/smoke.min.js: -------------------------------------------------------------------------------- 1 | eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('7 2={N:[],I:9,S:1o,i:0,Y:6(a){7 b=8.1k(\'o\');b.1j(\'B\',\'2-Q-\'+a);b.10=\'2-X\';b.1m.1n=2.S;2.S++;8.1p.1r(b)},H:6(){7 a=1s 1t().1x();a=1z.1D(1,1G)+a;3(!2.I){2.q(F,"U",6(){2.Y(a)})}w{2.Y(a)}C a},1H:6(){},G:6(e,f){2.i++;f.1I=2.i;e=e.15(/\\n/g,\'<1c />\');e=e.15(/\\r/g,\'<1c />\');7 a=\'\';3(f.5==\'t\'){a=\'\'+\'\'+\'\'}7 b=\'1A\';7 c=\'1y\';7 d=\'\';3(f.p.u){b=f.p.u}3(f.p.y){c=f.p.y}3(f.p.14){d=f.p.14}7 g=\'\';3(f.5!=\'L\'){g=\'\';3(f.5==\'E\'){g+=\'\'+b+\'\'}3(f.5==\'t\'||f.5==\'z\'){g+=\'\'+c+\'\'+\'\'+b+\'\'}g+=\'\'}7 h=\'\'+\'\'+\'\'+e+a+g+\'\'+\'\';3(!2.I){2.q(F,"U",6(){2.Z(e,f,h)})}w{2.Z(e,f,h)}},Z:6(e,f,a){7 b=8.l(\'2-Q-\'+f.4+\'\');b.10=\'2-X 2-1g\';b.K=a;1h(b.K==""){b.K=a}3(2.N[f.4]){1i(2.N[f.4])}7 g=8.l(\'2-1a-\'+f.4+\'\');2.q(g,"s",6(){2.k(f.5,f.4);3(f.5==\'t\'||f.5==\'z\'){f.m(9)}});3(f.5==\'E\'){7 h=8.l(\'E-u-\'+f.4+\'\');2.q(h,"s",6(){2.k(f.5,f.4)});8.O=6(e){3(!e)e=F.W;3(e.v==13||e.v==16||e.v==V){2.k(f.5,f.4)}}}3(f.5==\'z\'){7 h=8.l(\'z-y-\'+f.4+\'\');2.q(h,"s",6(){2.k(f.5,f.4);f.m(9)});7 i=8.l(\'z-u-\'+f.4+\'\');2.q(i,"s",6(){2.k(f.5,f.4);f.m(P)});8.O=6(e){3(!e)e=F.W;3(e.v==13||e.v==16){2.k(f.5,f.4);f.m(P)}w 3(e.v==V){2.k(f.5,f.4);f.m(9)}}}3(f.5==\'t\'){7 c=8.l(\'x-M-\'+f.4+\'\');11(6(){c.1u();c.1f()},1w);7 h=8.l(\'t-y-\'+f.4+\'\');2.q(h,"s",6(){2.k(f.5,f.4);f.m(9)});7 j=8.l(\'x-M-\'+f.4+\'\');7 i=8.l(\'t-u-\'+f.4+\'\');2.q(i,"s",6(){2.k(f.5,f.4);f.m(j.1b)});8.O=6(e){3(!e)e=F.W;3(e.v==13){2.k(f.5,f.4);f.m(j.1b)}w 3(e.v==V){2.k(f.5,f.4);f.m(9)}}}3(f.5==\'L\'){2.N[f.4]=11(6(){2.k(f.5,f.4)},f.19)}},k:6(a,b){7 c=8.l(\'2-Q-\'+b);c.10=\'2-X\';3(g=8.l(a+\'-u-\'+b)){2.T(g,"s",6(){});8.O=1B}3(h=8.l(a+\'-y-\'+b)){2.T(h,"s",6(){})}2.i=0;c.K=\'\'},E:6(e,f){3(J(f)!=\'R\'){f=9}7 a=2.H();2.G(e,{5:\'E\',p:f,4:a})},L:6(e,f){3(J(f)==\'1E\'){f=1F}7 a=2.H();2.G(e,{5:\'L\',19:f,p:9,4:a})},z:6(e,f,g){3(J(g)!=\'R\'){g=9}7 a=2.H();2.G(e,{5:\'z\',m:f,p:g,4:a})},t:6(e,f,g){3(J(g)!=\'R\'){g=9}7 a=2.H();C 2.G(e,{5:\'t\',m:f,p:g,4:a})},q:6(e,f,g){3(e.12){e.12(f,g,9)}w 3(e.1e){7 r=e.1e(\'18\'+f,g);C r}w{C 9}},T:6(e,f,g){3(e.17){e.17("s",g,9)}w 3(e.1d){7 r=e.1d(\'18\'+f,g);C r}w{C 9}}};3(!2.I){2.q(F,"U",6(){2.I=P})}',62,107,'||smoke|if|newid|type|function|var|document|false|||||||||||destroy|getElementById|callback||div|params|listen||click|prompt|ok|keyCode|else|dialog|cancel|confirm|class|id|return|button|alert|window|build|newdialog|init|typeof|innerHTML|signal|input|smoketimeout|onkeyup|true|out|object|zindex|stoplistening|load|27|event|base|bodyload|finishbuild|className|setTimeout|addEventListener||classname|replace|32|removeEventListener|on|timeout|bg|value|br|detachEvent|attachEvent|select|visible|while|clearTimeout|setAttribute|createElement|smokebg|style|zIndex|1000|body|buttons|appendChild|new|Date|focus|inner|100|getTime|Cancel|Math|OK|null|text|random|undefined|15000|99|forceload|stack'.split('|'),0,{})) 2 | -------------------------------------------------------------------------------- /static/themes/dark.css: -------------------------------------------------------------------------------- 1 | .smoke-base { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | bottom: 0; 6 | right: 0; 7 | visibility: hidden; 8 | opacity: 0; 9 | -moz-transition: all .3s; 10 | -webkit-transition: opacity .3s; 11 | -o-transition: all .3s; 12 | transition: all .3s; 13 | } 14 | .smoke-base .dialog { 15 | top: 25%; 16 | left: 0%; 17 | width: 100%; 18 | } 19 | 20 | .smoke-base .dialog-inner { 21 | padding: 15px; 22 | } 23 | 24 | .smoke { 25 | text-transform: none; 26 | color:#000000; 27 | font-weight: normal; 28 | background-color: rgba(f,f,f,.75); 29 | border-top:2px solid #333; 30 | border-bottom:2px solid #333; 31 | font: 48px/120% 'Georgia',Georgia,serif; 32 | text-shadow: 0 0 1px #666677; 33 | } 34 | 35 | .smoke #sub{ 36 | text-transform: none; 37 | color:#000000; 38 | font-weight: normal; 39 | background-color: rgba(f,f,f,.75); 40 | font: 28px/120% 'Georgia',Georgia,serif; 41 | text-shadow: 0 0 1px #666677; 42 | padding: 15px; 43 | } 44 | .dialog-buttons { 45 | margin: 5px 0 5px 0; 46 | } 47 | 48 | .dialog-buttons button { 49 | border-radius: 3px; 50 | text-transform: uppercase; 51 | background-color: #ebebeb; 52 | color: #333; 53 | font-size:11px; 54 | padding:6px 9px; 55 | border:1px solid #333; 56 | } 57 | 58 | button.cancel { 59 | background-color: #999; 60 | color:#222 61 | } 62 | 63 | .dialog-prompt input{ 64 | width:300px; 65 | text-align:center; 66 | background-color:#ccc; 67 | } 68 | --------------------------------------------------------------------------------