@@ -29,6 +29,10 @@ class CourseDescriptor(SequenceDescriptor):
|
||||
@property
|
||||
def instructors(self):
|
||||
return self.get_about_section("instructors").split("\n")
|
||||
|
||||
@property
|
||||
def wiki_namespace(self):
|
||||
return self.location.course
|
||||
|
||||
def get_about_section(self, section_key):
|
||||
"""
|
||||
|
||||
@@ -14,6 +14,7 @@ _FIELDS = ['number', # 6.002x
|
||||
'path', # /some/absolute/filepath/6.002x --> course.xml is in here.
|
||||
'instructors', # ['Anant Agarwal']
|
||||
'institution', # "MIT"
|
||||
'wiki_namespace',
|
||||
'grader', # a courseware.graders.CourseGrader object
|
||||
|
||||
#'start', # These should be datetime fields
|
||||
|
||||
@@ -34,7 +34,7 @@ class ArticleAdminForm(forms.ModelForm):
|
||||
model = Article
|
||||
|
||||
class ArticleAdmin(admin.ModelAdmin):
|
||||
list_display = ('created_by', 'slug', 'modified_on', 'parent')
|
||||
list_display = ('created_by', 'slug', 'modified_on', 'namespace')
|
||||
search_fields = ('slug',)
|
||||
prepopulated_fields = {'slug': ('title',) }
|
||||
inlines = [RevisionInline]
|
||||
|
||||
@@ -17,8 +17,6 @@ circuit-schematic:[["r",[128,48,0],{"r":"1","_json_":0},["2","1"]],["view",0,0,2
|
||||
import markdown
|
||||
import re
|
||||
|
||||
import simplewiki.settings as settings
|
||||
|
||||
from django.utils.html import escape
|
||||
|
||||
try:
|
||||
@@ -68,5 +66,7 @@ class CircuitLink(markdown.inlinepatterns.Pattern):
|
||||
return etree.fromstring("<div align='center'><input type='hidden' parts='' value='" + data + "' analyses='' class='schematic ctrls' width='640' height='480'/></div>")
|
||||
|
||||
|
||||
def makeExtension(configs=None) :
|
||||
return CircuitExtension(configs=configs)
|
||||
def makeExtension(configs=None):
|
||||
to_return = CircuitExtension(configs=configs)
|
||||
print "circuit returning " , to_return
|
||||
return to_return
|
||||
|
||||
@@ -33,7 +33,7 @@ class WikiPathExtension(markdown.Extension):
|
||||
def __init__(self, configs):
|
||||
# set extension defaults
|
||||
self.config = {
|
||||
'base_url' : ['/', 'String to append to beginning or URL.'],
|
||||
'default_namespace' : ['edX', 'Default namespace for when one isn\'t specified.'],
|
||||
'html_class' : ['wikipath', 'CSS hook. Leave blank for none.']
|
||||
}
|
||||
|
||||
@@ -62,7 +62,10 @@ class WikiPath(markdown.inlinepatterns.Pattern):
|
||||
if article_title.startswith("/"):
|
||||
article_title = article_title[1:]
|
||||
|
||||
url = self.config['base_url'][0] + article_title
|
||||
if not "/" in article_title:
|
||||
article_title = self.config['default_namespace'][0] + "/" + article_title
|
||||
|
||||
url = "../" + article_title
|
||||
label = m.group('linkTitle')
|
||||
a = etree.Element('a')
|
||||
a.set('href', url)
|
||||
|
||||
218
lms/djangoapps/simplewiki/migrations/0001_initial.py
Normal file
218
lms/djangoapps/simplewiki/migrations/0001_initial.py
Normal file
@@ -0,0 +1,218 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'Article'
|
||||
db.create_table('simplewiki_article', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('title', self.gf('django.db.models.fields.CharField')(max_length=512)),
|
||||
('slug', self.gf('django.db.models.fields.SlugField')(max_length=100, blank=True)),
|
||||
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
|
||||
('created_on', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=1, blank=True)),
|
||||
('modified_on', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=1, blank=True)),
|
||||
('parent', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Article'], null=True, blank=True)),
|
||||
('locked', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
('permissions', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Permission'], null=True, blank=True)),
|
||||
('current_revision', self.gf('django.db.models.fields.related.OneToOneField')(blank=True, related_name='current_rev', unique=True, null=True, to=orm['simplewiki.Revision'])),
|
||||
))
|
||||
db.send_create_signal('simplewiki', ['Article'])
|
||||
|
||||
# Adding unique constraint on 'Article', fields ['slug', 'parent']
|
||||
db.create_unique('simplewiki_article', ['slug', 'parent_id'])
|
||||
|
||||
# Adding M2M table for field related on 'Article'
|
||||
db.create_table('simplewiki_article_related', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('from_article', models.ForeignKey(orm['simplewiki.article'], null=False)),
|
||||
('to_article', models.ForeignKey(orm['simplewiki.article'], null=False))
|
||||
))
|
||||
db.create_unique('simplewiki_article_related', ['from_article_id', 'to_article_id'])
|
||||
|
||||
# Adding model 'ArticleAttachment'
|
||||
db.create_table('simplewiki_articleattachment', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('article', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Article'])),
|
||||
('file', self.gf('django.db.models.fields.files.FileField')(max_length=255)),
|
||||
('uploaded_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
|
||||
('uploaded_on', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||
))
|
||||
db.send_create_signal('simplewiki', ['ArticleAttachment'])
|
||||
|
||||
# Adding model 'Revision'
|
||||
db.create_table('simplewiki_revision', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('article', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Article'])),
|
||||
('revision_text', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
|
||||
('revision_user', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='wiki_revision_user', null=True, to=orm['auth.User'])),
|
||||
('revision_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||
('contents', self.gf('django.db.models.fields.TextField')()),
|
||||
('contents_parsed', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
|
||||
('counter', self.gf('django.db.models.fields.IntegerField')(default=1)),
|
||||
('previous_revision', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Revision'], null=True, blank=True)),
|
||||
('deleted', self.gf('django.db.models.fields.IntegerField')(default=0)),
|
||||
))
|
||||
db.send_create_signal('simplewiki', ['Revision'])
|
||||
|
||||
# Adding model 'Permission'
|
||||
db.create_table('simplewiki_permission', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('permission_name', self.gf('django.db.models.fields.CharField')(max_length=255)),
|
||||
))
|
||||
db.send_create_signal('simplewiki', ['Permission'])
|
||||
|
||||
# Adding M2M table for field can_write on 'Permission'
|
||||
db.create_table('simplewiki_permission_can_write', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('permission', models.ForeignKey(orm['simplewiki.permission'], null=False)),
|
||||
('user', models.ForeignKey(orm['auth.user'], null=False))
|
||||
))
|
||||
db.create_unique('simplewiki_permission_can_write', ['permission_id', 'user_id'])
|
||||
|
||||
# Adding M2M table for field can_read on 'Permission'
|
||||
db.create_table('simplewiki_permission_can_read', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('permission', models.ForeignKey(orm['simplewiki.permission'], null=False)),
|
||||
('user', models.ForeignKey(orm['auth.user'], null=False))
|
||||
))
|
||||
db.create_unique('simplewiki_permission_can_read', ['permission_id', 'user_id'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'Article', fields ['slug', 'parent']
|
||||
db.delete_unique('simplewiki_article', ['slug', 'parent_id'])
|
||||
|
||||
# Deleting model 'Article'
|
||||
db.delete_table('simplewiki_article')
|
||||
|
||||
# Removing M2M table for field related on 'Article'
|
||||
db.delete_table('simplewiki_article_related')
|
||||
|
||||
# Deleting model 'ArticleAttachment'
|
||||
db.delete_table('simplewiki_articleattachment')
|
||||
|
||||
# Deleting model 'Revision'
|
||||
db.delete_table('simplewiki_revision')
|
||||
|
||||
# Deleting model 'Permission'
|
||||
db.delete_table('simplewiki_permission')
|
||||
|
||||
# Removing M2M table for field can_write on 'Permission'
|
||||
db.delete_table('simplewiki_permission_can_write')
|
||||
|
||||
# Removing M2M table for field can_read on 'Permission'
|
||||
db.delete_table('simplewiki_permission_can_read')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
|
||||
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
|
||||
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
|
||||
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
|
||||
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'simplewiki.article': {
|
||||
'Meta': {'unique_together': "(('slug', 'parent'),)", 'object_name': 'Article'},
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
|
||||
'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
|
||||
'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
|
||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']", 'null': 'True', 'blank': 'True'}),
|
||||
'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}),
|
||||
'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '512'})
|
||||
},
|
||||
'simplewiki.articleattachment': {
|
||||
'Meta': {'object_name': 'ArticleAttachment'},
|
||||
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
|
||||
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
|
||||
'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
|
||||
},
|
||||
'simplewiki.permission': {
|
||||
'Meta': {'object_name': 'Permission'},
|
||||
'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
},
|
||||
'simplewiki.revision': {
|
||||
'Meta': {'object_name': 'Revision'},
|
||||
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
|
||||
'contents': ('django.db.models.fields.TextField', [], {}),
|
||||
'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
|
||||
'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}),
|
||||
'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['simplewiki']
|
||||
135
lms/djangoapps/simplewiki/migrations/0002_unique_slugs.py
Normal file
135
lms/djangoapps/simplewiki/migrations/0002_unique_slugs.py
Normal file
@@ -0,0 +1,135 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import DataMigration
|
||||
from django.db import models
|
||||
|
||||
class Migration(DataMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# We collect every article slug in a set. Any time we see a duplicate, we change the duplicate's name
|
||||
unique_slugs = set()
|
||||
for article in orm.Article.objects.all():
|
||||
if article.slug in unique_slugs:
|
||||
i = 2
|
||||
new_name = article.slug + str(i)
|
||||
while new_name in unique_slugs:
|
||||
i += 1
|
||||
new_name = article.slug + str(i)
|
||||
print "Changing" , article.slug , "to" , new_name
|
||||
article.slug = new_name
|
||||
article.save()
|
||||
|
||||
unique_slugs.add( article.slug )
|
||||
|
||||
def backwards(self, orm):
|
||||
"Write your backwards methods here."
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
|
||||
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
|
||||
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
|
||||
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
|
||||
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'simplewiki.article': {
|
||||
'Meta': {'unique_together': "(('slug', 'parent'),)", 'object_name': 'Article'},
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
|
||||
'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
|
||||
'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
|
||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']", 'null': 'True', 'blank': 'True'}),
|
||||
'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}),
|
||||
'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '512'})
|
||||
},
|
||||
'simplewiki.articleattachment': {
|
||||
'Meta': {'object_name': 'ArticleAttachment'},
|
||||
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
|
||||
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
|
||||
'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
|
||||
},
|
||||
'simplewiki.permission': {
|
||||
'Meta': {'object_name': 'Permission'},
|
||||
'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
},
|
||||
'simplewiki.revision': {
|
||||
'Meta': {'object_name': 'Revision'},
|
||||
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
|
||||
'contents': ('django.db.models.fields.TextField', [], {}),
|
||||
'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
|
||||
'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}),
|
||||
'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['simplewiki']
|
||||
symmetrical = True
|
||||
@@ -0,0 +1,163 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Removing unique constraint on 'Article', fields ['slug', 'parent']
|
||||
db.delete_unique('simplewiki_article', ['slug', 'parent_id'])
|
||||
|
||||
# Adding model 'Namespace'
|
||||
db.create_table('simplewiki_namespace', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('name', self.gf('django.db.models.fields.CharField')(max_length=30)),
|
||||
))
|
||||
db.send_create_signal('simplewiki', ['Namespace'])
|
||||
|
||||
# Deleting field 'Article.parent'
|
||||
db.delete_column('simplewiki_article', 'parent_id')
|
||||
|
||||
# Adding field 'Article.namespace'
|
||||
db.add_column('simplewiki_article', 'namespace',
|
||||
self.gf('django.db.models.fields.related.ForeignKey')(default=1, to=orm['simplewiki.Namespace']),
|
||||
keep_default=False)
|
||||
|
||||
# Adding unique constraint on 'Article', fields ['namespace', 'slug']
|
||||
db.create_unique('simplewiki_article', ['namespace_id', 'slug'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'Article', fields ['namespace', 'slug']
|
||||
db.delete_unique('simplewiki_article', ['namespace_id', 'slug'])
|
||||
|
||||
# Deleting model 'Namespace'
|
||||
db.delete_table('simplewiki_namespace')
|
||||
|
||||
# Adding field 'Article.parent'
|
||||
db.add_column('simplewiki_article', 'parent',
|
||||
self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Article'], null=True, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
# Deleting field 'Article.namespace'
|
||||
db.delete_column('simplewiki_article', 'namespace_id')
|
||||
|
||||
# Adding unique constraint on 'Article', fields ['slug', 'parent']
|
||||
db.create_unique('simplewiki_article', ['slug', 'parent_id'])
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
|
||||
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
|
||||
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
|
||||
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
|
||||
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'simplewiki.article': {
|
||||
'Meta': {'unique_together': "(('slug', 'namespace'),)", 'object_name': 'Article'},
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
|
||||
'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
|
||||
'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
|
||||
'namespace': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Namespace']"}),
|
||||
'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}),
|
||||
'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '512'})
|
||||
},
|
||||
'simplewiki.articleattachment': {
|
||||
'Meta': {'object_name': 'ArticleAttachment'},
|
||||
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
|
||||
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
|
||||
'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
|
||||
},
|
||||
'simplewiki.namespace': {
|
||||
'Meta': {'object_name': 'Namespace'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '30'})
|
||||
},
|
||||
'simplewiki.permission': {
|
||||
'Meta': {'object_name': 'Permission'},
|
||||
'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
},
|
||||
'simplewiki.revision': {
|
||||
'Meta': {'object_name': 'Revision'},
|
||||
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
|
||||
'contents': ('django.db.models.fields.TextField', [], {}),
|
||||
'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
|
||||
'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}),
|
||||
'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['simplewiki']
|
||||
@@ -0,0 +1,133 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import DataMigration
|
||||
from django.db import models
|
||||
|
||||
class Migration(DataMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
namespace6002x, created = orm.Namespace.objects.get_or_create(name="6.002xS12")
|
||||
if created:
|
||||
namespace6002x.save()
|
||||
|
||||
for article in orm.Article.objects.all():
|
||||
article.namespace = namespace6002x
|
||||
article.save()
|
||||
|
||||
def backwards(self, orm):
|
||||
raise RuntimeError("Cannot reverse this migration.")
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
|
||||
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
|
||||
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
|
||||
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
|
||||
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'simplewiki.article': {
|
||||
'Meta': {'unique_together': "(('slug', 'namespace'),)", 'object_name': 'Article'},
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
|
||||
'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
|
||||
'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
|
||||
'namespace': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Namespace']"}),
|
||||
'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}),
|
||||
'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '512'})
|
||||
},
|
||||
'simplewiki.articleattachment': {
|
||||
'Meta': {'object_name': 'ArticleAttachment'},
|
||||
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
|
||||
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
|
||||
'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
|
||||
},
|
||||
'simplewiki.namespace': {
|
||||
'Meta': {'object_name': 'Namespace'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '30'})
|
||||
},
|
||||
'simplewiki.permission': {
|
||||
'Meta': {'object_name': 'Permission'},
|
||||
'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
},
|
||||
'simplewiki.revision': {
|
||||
'Meta': {'object_name': 'Revision'},
|
||||
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
|
||||
'contents': ('django.db.models.fields.TextField', [], {}),
|
||||
'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
|
||||
'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}),
|
||||
'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['simplewiki']
|
||||
symmetrical = True
|
||||
@@ -0,0 +1,131 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding unique constraint on 'Namespace', fields ['name']
|
||||
db.create_unique('simplewiki_namespace', ['name'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'Namespace', fields ['name']
|
||||
db.delete_unique('simplewiki_namespace', ['name'])
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
|
||||
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
|
||||
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
|
||||
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
|
||||
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'simplewiki.article': {
|
||||
'Meta': {'unique_together': "(('slug', 'namespace'),)", 'object_name': 'Article'},
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
|
||||
'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
|
||||
'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
|
||||
'namespace': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Namespace']"}),
|
||||
'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}),
|
||||
'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '512'})
|
||||
},
|
||||
'simplewiki.articleattachment': {
|
||||
'Meta': {'object_name': 'ArticleAttachment'},
|
||||
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
|
||||
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
|
||||
'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
|
||||
},
|
||||
'simplewiki.namespace': {
|
||||
'Meta': {'object_name': 'Namespace'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'simplewiki.permission': {
|
||||
'Meta': {'object_name': 'Permission'},
|
||||
'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
},
|
||||
'simplewiki.revision': {
|
||||
'Meta': {'object_name': 'Revision'},
|
||||
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
|
||||
'contents': ('django.db.models.fields.TextField', [], {}),
|
||||
'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
|
||||
'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}),
|
||||
'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['simplewiki']
|
||||
131
lms/djangoapps/simplewiki/migrations/0006_auto.py
Normal file
131
lms/djangoapps/simplewiki/migrations/0006_auto.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding index on 'Namespace', fields ['name']
|
||||
db.create_index('simplewiki_namespace', ['name'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing index on 'Namespace', fields ['name']
|
||||
db.delete_index('simplewiki_namespace', ['name'])
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
|
||||
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
|
||||
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
|
||||
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
|
||||
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'simplewiki.article': {
|
||||
'Meta': {'unique_together': "(('slug', 'namespace'),)", 'object_name': 'Article'},
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
|
||||
'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
|
||||
'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
|
||||
'namespace': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Namespace']"}),
|
||||
'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}),
|
||||
'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '512'})
|
||||
},
|
||||
'simplewiki.articleattachment': {
|
||||
'Meta': {'object_name': 'ArticleAttachment'},
|
||||
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
|
||||
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
|
||||
'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
|
||||
},
|
||||
'simplewiki.namespace': {
|
||||
'Meta': {'object_name': 'Namespace'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30', 'db_index': 'True'})
|
||||
},
|
||||
'simplewiki.permission': {
|
||||
'Meta': {'object_name': 'Permission'},
|
||||
'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
},
|
||||
'simplewiki.revision': {
|
||||
'Meta': {'object_name': 'Revision'},
|
||||
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
|
||||
'contents': ('django.db.models.fields.TextField', [], {}),
|
||||
'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
|
||||
'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}),
|
||||
'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['simplewiki']
|
||||
0
lms/djangoapps/simplewiki/migrations/__init__.py
Normal file
0
lms/djangoapps/simplewiki/migrations/__init__.py
Normal file
367
lms/djangoapps/simplewiki/models copy.py
Normal file
367
lms/djangoapps/simplewiki/models copy.py
Normal file
@@ -0,0 +1,367 @@
|
||||
import difflib
|
||||
import os
|
||||
|
||||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.db.models import signals
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from markdown import markdown
|
||||
|
||||
from wiki_settings import *
|
||||
from util.cache import cache
|
||||
|
||||
|
||||
class ShouldHaveExactlyOneRootSlug(Exception):
|
||||
pass
|
||||
|
||||
class Article(models.Model):
|
||||
"""Wiki article referring to Revision model for actual content.
|
||||
'slug' and 'parent' field should be maintained centrally, since users
|
||||
aren't allowed to change them, anyways.
|
||||
"""
|
||||
|
||||
title = models.CharField(max_length=512, verbose_name=_('Article title'),
|
||||
blank=False)
|
||||
slug = models.SlugField(max_length=100, verbose_name=_('slug'),
|
||||
help_text=_('Letters, numbers, underscore and hyphen.'),
|
||||
blank=True)
|
||||
created_by = models.ForeignKey(User, verbose_name=_('Created by'), blank=True, null=True)
|
||||
created_on = models.DateTimeField(auto_now_add = 1)
|
||||
modified_on = models.DateTimeField(auto_now_add = 1)
|
||||
parent = models.ForeignKey('self', verbose_name=_('Parent article slug'),
|
||||
help_text=_('Affects URL structure and possibly inherits permissions'),
|
||||
null=True, blank=True)
|
||||
locked = models.BooleanField(default=False, verbose_name=_('Locked for editing'))
|
||||
permissions = models.ForeignKey('Permission', verbose_name=_('Permissions'),
|
||||
blank=True, null=True,
|
||||
help_text=_('Permission group'))
|
||||
current_revision = models.OneToOneField('Revision', related_name='current_rev',
|
||||
blank=True, null=True, editable=True)
|
||||
related = models.ManyToManyField('self', verbose_name=_('Related articles'), symmetrical=True,
|
||||
help_text=_('Sets a symmetrical relation other articles'),
|
||||
blank=True, null=True)
|
||||
|
||||
def attachments(self):
|
||||
return ArticleAttachment.objects.filter(article__exact = self)
|
||||
|
||||
@classmethod
|
||||
def get_root(cls):
|
||||
"""Return the root article, which should ALWAYS exist..
|
||||
except the very first time the wiki is loaded, in which
|
||||
case the user is prompted to create this article."""
|
||||
try:
|
||||
return Article.objects.filter(slug__exact = "")[0]
|
||||
except:
|
||||
raise ShouldHaveExactlyOneRootSlug()
|
||||
|
||||
def get_url(self):
|
||||
"""Return the Wiki URL for an article"""
|
||||
url = self.slug + "/"
|
||||
if self.parent_id:
|
||||
parent_url = cache.get("wiki_url-" + str(self.parent_id))
|
||||
if parent_url is None:
|
||||
parent_url = self.parent.get_url()
|
||||
|
||||
url = parent_url + url
|
||||
|
||||
cache.set("wiki_url-" + str(self.id), url, 60*60)
|
||||
|
||||
return url
|
||||
|
||||
def get_abs_url(self):
|
||||
"""Return the absolute path for an article. This is necessary in cases
|
||||
where the template system isn't used for generating URLs..."""
|
||||
# TODO: Remove and create a reverse() lookup.
|
||||
return WIKI_BASE + self.get_url()
|
||||
|
||||
@models.permalink
|
||||
def get_absolute_url(self):
|
||||
return ('wiki_view', [self.get_url()])
|
||||
|
||||
@classmethod
|
||||
def get_url_reverse(cls, path, article, return_list=[]):
|
||||
"""Lookup a URL and return the corresponding set of articles
|
||||
in the path."""
|
||||
if path == []:
|
||||
return return_list + [article]
|
||||
# Lookup next child in path
|
||||
try:
|
||||
a = Article.objects.get(parent__exact = article, slug__exact=str(path[0]))
|
||||
return cls.get_url_reverse(path[1:], a, return_list+[article])
|
||||
except Exception, e:
|
||||
return None
|
||||
|
||||
def can_read(self, user):
|
||||
""" Check read permissions and return True/False."""
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if self.permissions:
|
||||
perms = self.permissions.can_read.all()
|
||||
return perms.count() == 0 or (user in perms)
|
||||
else:
|
||||
return self.parent.can_read(user) if self.parent else True
|
||||
|
||||
def can_write(self, user):
|
||||
""" Check write permissions and return True/False."""
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if self.permissions:
|
||||
perms = self.permissions.can_write.all()
|
||||
return perms.count() == 0 or (user in perms)
|
||||
else:
|
||||
return self.parent.can_write(user) if self.parent else True
|
||||
|
||||
def can_write_l(self, user):
|
||||
"""Check write permissions and locked status"""
|
||||
if user.is_superuser:
|
||||
return True
|
||||
return not self.locked and self.can_write(user)
|
||||
|
||||
def can_attach(self, user):
|
||||
return self.can_write_l(user) and (WIKI_ALLOW_ANON_ATTACHMENTS or not user.is_anonymous())
|
||||
|
||||
def __unicode__(self):
|
||||
if self.slug == '' and not self.parent:
|
||||
return unicode(_('Root article'))
|
||||
else:
|
||||
return self.get_url()
|
||||
|
||||
class Meta:
|
||||
unique_together = (('slug', 'parent'),)
|
||||
verbose_name = _('Article')
|
||||
verbose_name_plural = _('Articles')
|
||||
|
||||
def get_attachment_filepath(instance, filename):
|
||||
"""Store file, appending new extension for added security"""
|
||||
dir_ = WIKI_ATTACHMENTS + instance.article.get_url()
|
||||
dir_ = '/'.join(filter(lambda x: x!='', dir_.split('/')))
|
||||
if not os.path.exists(WIKI_ATTACHMENTS_ROOT + dir_):
|
||||
os.makedirs(WIKI_ATTACHMENTS_ROOT + dir_)
|
||||
return dir_ + '/' + filename + '.upload'
|
||||
|
||||
class ArticleAttachment(models.Model):
|
||||
article = models.ForeignKey(Article, verbose_name=_('Article'))
|
||||
file = models.FileField(max_length=255, upload_to=get_attachment_filepath, verbose_name=_('Attachment'))
|
||||
uploaded_by = models.ForeignKey(User, blank=True, verbose_name=_('Uploaded by'), null=True)
|
||||
uploaded_on = models.DateTimeField(auto_now_add = True, verbose_name=_('Upload date'))
|
||||
|
||||
def download_url(self):
|
||||
return reverse('wiki_view_attachment', args=(self.article.get_url(), self.filename()))
|
||||
|
||||
def filename(self):
|
||||
return '.'.join(self.file.name.split('/')[-1].split('.')[:-1])
|
||||
|
||||
def get_size(self):
|
||||
try:
|
||||
size = self.file.size
|
||||
except OSError:
|
||||
size = 0
|
||||
return size
|
||||
|
||||
def filename(self):
|
||||
return '.'.join(self.file.name.split('/')[-1].split('.')[:-1])
|
||||
|
||||
def is_image(self):
|
||||
fname = self.filename().split('.')
|
||||
if len(fname) > 1 and fname[-1].lower() in WIKI_IMAGE_EXTENSIONS:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_thumb(self):
|
||||
return self.get_thumb_impl(*WIKI_IMAGE_THUMB_SIZE)
|
||||
|
||||
def get_thumb_small(self):
|
||||
return self.get_thumb_impl(*WIKI_IMAGE_THUMB_SIZE_SMALL)
|
||||
|
||||
def mk_thumbs(self):
|
||||
self.mk_thumb(*WIKI_IMAGE_THUMB_SIZE, **{'force':True})
|
||||
self.mk_thumb(*WIKI_IMAGE_THUMB_SIZE_SMALL, **{'force':True})
|
||||
|
||||
def mk_thumb(self, width, height, force=False):
|
||||
"""Requires Python Imaging Library (PIL)"""
|
||||
if not self.get_size():
|
||||
return False
|
||||
|
||||
if not self.is_image():
|
||||
return False
|
||||
|
||||
base_path = os.path.dirname(self.file.path)
|
||||
orig_name = self.filename().split('.')
|
||||
thumb_filename = "%s__thumb__%d_%d.%s" % ('.'.join(orig_name[:-1]), width, height, orig_name[-1])
|
||||
thumb_filepath = "%s%s%s" % (base_path, os.sep, thumb_filename)
|
||||
|
||||
if force or not os.path.exists(thumb_filepath):
|
||||
try:
|
||||
import Image
|
||||
img = Image.open(self.file.path)
|
||||
img.thumbnail((width,height), Image.ANTIALIAS)
|
||||
img.save(thumb_filepath)
|
||||
except IOError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_thumb_impl(self, width, height):
|
||||
"""Requires Python Imaging Library (PIL)"""
|
||||
|
||||
if not self.get_size():
|
||||
return False
|
||||
|
||||
if not self.is_image():
|
||||
return False
|
||||
|
||||
self.mk_thumb(width, height)
|
||||
|
||||
orig_name = self.filename().split('.')
|
||||
thumb_filename = "%s__thumb__%d_%d.%s" % ('.'.join(orig_name[:-1]), width, height, orig_name[-1])
|
||||
thumb_url = settings.MEDIA_URL + WIKI_ATTACHMENTS + self.article.get_url() +'/' + thumb_filename
|
||||
|
||||
return thumb_url
|
||||
|
||||
def __unicode__(self):
|
||||
return self.filename()
|
||||
|
||||
class Revision(models.Model):
|
||||
|
||||
article = models.ForeignKey(Article, verbose_name=_('Article'))
|
||||
revision_text = models.CharField(max_length=255, blank=True, null=True,
|
||||
verbose_name=_('Description of change'))
|
||||
revision_user = models.ForeignKey(User, verbose_name=_('Modified by'),
|
||||
blank=True, null=True, related_name='wiki_revision_user')
|
||||
revision_date = models.DateTimeField(auto_now_add = True, verbose_name=_('Revision date'))
|
||||
contents = models.TextField(verbose_name=_('Contents (Use MarkDown format)'))
|
||||
contents_parsed = models.TextField(editable=False, blank=True, null=True)
|
||||
counter = models.IntegerField(verbose_name=_('Revision#'), default=1, editable=False)
|
||||
previous_revision = models.ForeignKey('self', blank=True, null=True, editable=False)
|
||||
|
||||
# Deleted has three values. 0 is normal, non-deleted. 1 is if it was deleted by a normal user. It should
|
||||
# be a NEW revision, so that it appears in the history. 2 is a special flag that can be applied or removed
|
||||
# from a normal revision. It means it has been admin-deleted, and can only been seen by an admin. It doesn't
|
||||
# show up in the history.
|
||||
deleted = models.IntegerField(verbose_name=_('Deleted group'), default=0)
|
||||
|
||||
def get_user(self):
|
||||
return self.revision_user if self.revision_user else _('Anonymous')
|
||||
|
||||
# Called after the deleted fied has been changed (between 0 and 2). This bypasses the normal checks put in
|
||||
# save that update the revision or reject the save if contents haven't changed
|
||||
def adminSetDeleted(self, deleted):
|
||||
self.deleted = deleted
|
||||
super(Revision, self).save()
|
||||
|
||||
def save(self, **kwargs):
|
||||
# Check if contents have changed... if not, silently ignore save
|
||||
if self.article and self.article.current_revision:
|
||||
if self.deleted == 0 and self.article.current_revision.contents == self.contents:
|
||||
return
|
||||
else:
|
||||
import datetime
|
||||
self.article.modified_on = datetime.datetime.now()
|
||||
self.article.save()
|
||||
|
||||
# Increment counter according to previous revision
|
||||
previous_revision = Revision.objects.filter(article=self.article).order_by('-counter')
|
||||
if previous_revision.count() > 0:
|
||||
if previous_revision.count() > previous_revision[0].counter:
|
||||
self.counter = previous_revision.count() + 1
|
||||
else:
|
||||
self.counter = previous_revision[0].counter + 1
|
||||
else:
|
||||
self.counter = 1
|
||||
if (self.article.current_revision and self.article.current_revision.deleted == 0):
|
||||
self.previous_revision = self.article.current_revision
|
||||
|
||||
# Create pre-parsed contents - no need to parse on-the-fly
|
||||
ext = WIKI_MARKDOWN_EXTENSIONS
|
||||
ext += ["wikipath(base_url=%s)" % reverse('wiki_view', args=('/',))]
|
||||
self.contents_parsed = markdown(self.contents,
|
||||
extensions=ext,
|
||||
safe_mode='escape',)
|
||||
super(Revision, self).save(**kwargs)
|
||||
|
||||
def delete(self, **kwargs):
|
||||
"""If a current revision is deleted, then regress to the previous
|
||||
revision or insert a stub, if no other revisions are available"""
|
||||
article = self.article
|
||||
if article.current_revision == self:
|
||||
prev_revision = Revision.objects.filter(article__exact = article,
|
||||
pk__not = self.pk).order_by('-counter')
|
||||
if prev_revision:
|
||||
article.current_revision = prev_revision[0]
|
||||
article.save()
|
||||
else:
|
||||
r = Revision(article=article,
|
||||
revision_user = article.created_by)
|
||||
r.contents = unicode(_('Auto-generated stub'))
|
||||
r.revision_text= unicode(_('Auto-generated stub'))
|
||||
r.save()
|
||||
article.current_revision = r
|
||||
article.save()
|
||||
super(Revision, self).delete(**kwargs)
|
||||
|
||||
def get_diff(self):
|
||||
if (self.deleted == 1):
|
||||
yield "Article Deletion"
|
||||
return
|
||||
|
||||
if self.previous_revision:
|
||||
previous = self.previous_revision.contents.splitlines(1)
|
||||
else:
|
||||
previous = []
|
||||
|
||||
# Todo: difflib.HtmlDiff would look pretty for our history pages!
|
||||
diff = difflib.unified_diff(previous, self.contents.splitlines(1))
|
||||
# let's skip the preamble
|
||||
diff.next(); diff.next(); diff.next()
|
||||
|
||||
for d in diff:
|
||||
yield d
|
||||
|
||||
def __unicode__(self):
|
||||
return "r%d" % self.counter
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('article revision')
|
||||
verbose_name_plural = _('article revisions')
|
||||
|
||||
class Permission(models.Model):
|
||||
permission_name = models.CharField(max_length = 255, verbose_name=_('Permission name'))
|
||||
can_write = models.ManyToManyField(User, blank=True, null=True, related_name='write',
|
||||
help_text=_('Select none to grant anonymous access.'))
|
||||
can_read = models.ManyToManyField(User, blank=True, null=True, related_name='read',
|
||||
help_text=_('Select none to grant anonymous access.'))
|
||||
def __unicode__(self):
|
||||
return self.permission_name
|
||||
class Meta:
|
||||
verbose_name = _('Article permission')
|
||||
verbose_name_plural = _('Article permissions')
|
||||
|
||||
class RevisionForm(forms.ModelForm):
|
||||
contents = forms.CharField(label=_('Contents'), widget=forms.Textarea(attrs={'rows':8, 'cols':50}))
|
||||
class Meta:
|
||||
model = Revision
|
||||
fields = ['contents', 'revision_text']
|
||||
class RevisionFormWithTitle(forms.ModelForm):
|
||||
title = forms.CharField(label=_('Title'))
|
||||
class Meta:
|
||||
model = Revision
|
||||
fields = ['title', 'contents', 'revision_text']
|
||||
class CreateArticleForm(RevisionForm):
|
||||
title = forms.CharField(label=_('Title'))
|
||||
class Meta:
|
||||
model = Revision
|
||||
fields = ['title', 'contents',]
|
||||
|
||||
def set_revision(sender, *args, **kwargs):
|
||||
"""Signal handler to ensure that a new revision is always chosen as the
|
||||
current revision - automatically. It simplifies stuff greatly. Also
|
||||
stores previous revision for diff-purposes"""
|
||||
instance = kwargs['instance']
|
||||
created = kwargs['created']
|
||||
if created and instance.article:
|
||||
instance.article.current_revision = instance
|
||||
instance.article.save()
|
||||
|
||||
signals.post_save.connect(set_revision, Revision)
|
||||
@@ -16,9 +16,23 @@ from util.cache import cache
|
||||
class ShouldHaveExactlyOneRootSlug(Exception):
|
||||
pass
|
||||
|
||||
class Namespace(models.Model):
|
||||
name = models.CharField(max_length=30, db_index=True, unique=True, verbose_name=_('namespace'))
|
||||
# TODO: We may want to add permissions, etc later
|
||||
|
||||
@classmethod
|
||||
def ensure_namespace(cls, name):
|
||||
try:
|
||||
namespace = Namespace.objects.get(name__exact = name)
|
||||
except Namespace.DoesNotExist:
|
||||
new_namespace = Namespace(name=name)
|
||||
new_namespace.save()
|
||||
|
||||
|
||||
|
||||
class Article(models.Model):
|
||||
"""Wiki article referring to Revision model for actual content.
|
||||
'slug' and 'parent' field should be maintained centrally, since users
|
||||
'slug' and 'title' field should be maintained centrally, since users
|
||||
aren't allowed to change them, anyways.
|
||||
"""
|
||||
|
||||
@@ -27,12 +41,10 @@ class Article(models.Model):
|
||||
slug = models.SlugField(max_length=100, verbose_name=_('slug'),
|
||||
help_text=_('Letters, numbers, underscore and hyphen.'),
|
||||
blank=True)
|
||||
namespace = models.ForeignKey(Namespace, verbose_name=_('Namespace'))
|
||||
created_by = models.ForeignKey(User, verbose_name=_('Created by'), blank=True, null=True)
|
||||
created_on = models.DateTimeField(auto_now_add = 1)
|
||||
modified_on = models.DateTimeField(auto_now_add = 1)
|
||||
parent = models.ForeignKey('self', verbose_name=_('Parent article slug'),
|
||||
help_text=_('Affects URL structure and possibly inherits permissions'),
|
||||
null=True, blank=True)
|
||||
locked = models.BooleanField(default=False, verbose_name=_('Locked for editing'))
|
||||
permissions = models.ForeignKey('Permission', verbose_name=_('Permissions'),
|
||||
blank=True, null=True,
|
||||
@@ -45,53 +57,44 @@ class Article(models.Model):
|
||||
|
||||
def attachments(self):
|
||||
return ArticleAttachment.objects.filter(article__exact = self)
|
||||
|
||||
def get_path(self):
|
||||
return self.namespace.name + "/" + self.slug
|
||||
|
||||
@classmethod
|
||||
def get_article(cls, article_path):
|
||||
"""
|
||||
Given an article_path like namespace/slug, this returns the article. It may raise
|
||||
a Article.DoesNotExist if no matching article is found or ValueError if the
|
||||
article_path is not constructed properly.
|
||||
"""
|
||||
#TODO: Verify the path, throw a meaningful error?
|
||||
namespace, slug = article_path.split("/")
|
||||
return Article.objects.get( slug__exact = slug, namespace__name__exact = namespace)
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_root(cls):
|
||||
def get_root(cls, namespace):
|
||||
"""Return the root article, which should ALWAYS exist..
|
||||
except the very first time the wiki is loaded, in which
|
||||
case the user is prompted to create this article."""
|
||||
try:
|
||||
return Article.objects.filter(slug__exact = "")[0]
|
||||
return Article.objects.filter(slug__exact = "", namespace__name__exact = namespace)[0]
|
||||
except:
|
||||
raise ShouldHaveExactlyOneRootSlug()
|
||||
|
||||
def get_url(self):
|
||||
"""Return the Wiki URL for an article"""
|
||||
url = self.slug + "/"
|
||||
if self.parent_id:
|
||||
parent_url = cache.get("wiki_url-" + str(self.parent_id))
|
||||
if parent_url is None:
|
||||
parent_url = self.parent.get_url()
|
||||
|
||||
url = parent_url + url
|
||||
|
||||
cache.set("wiki_url-" + str(self.id), url, 60*60)
|
||||
|
||||
return url
|
||||
|
||||
def get_abs_url(self):
|
||||
"""Return the absolute path for an article. This is necessary in cases
|
||||
where the template system isn't used for generating URLs..."""
|
||||
# TODO: Remove and create a reverse() lookup.
|
||||
return WIKI_BASE + self.get_url()
|
||||
|
||||
@models.permalink
|
||||
def get_absolute_url(self):
|
||||
return ('wiki_view', [self.get_url()])
|
||||
|
||||
@classmethod
|
||||
def get_url_reverse(cls, path, article, return_list=[]):
|
||||
"""Lookup a URL and return the corresponding set of articles
|
||||
in the path."""
|
||||
if path == []:
|
||||
return return_list + [article]
|
||||
# Lookup next child in path
|
||||
try:
|
||||
a = Article.objects.get(parent__exact = article, slug__exact=str(path[0]))
|
||||
return cls.get_url_reverse(path[1:], a, return_list+[article])
|
||||
except Exception, e:
|
||||
return None
|
||||
# @classmethod
|
||||
# def get_url_reverse(cls, path, article, return_list=[]):
|
||||
# """Lookup a URL and return the corresponding set of articles
|
||||
# in the path."""
|
||||
# if path == []:
|
||||
# return return_list + [article]
|
||||
# # Lookup next child in path
|
||||
# try:
|
||||
# a = Article.objects.get(parent__exact = article, slug__exact=str(path[0]))
|
||||
# return cls.get_url_reverse(path[1:], a, return_list+[article])
|
||||
# except Exception, e:
|
||||
# return None
|
||||
|
||||
def can_read(self, user):
|
||||
""" Check read permissions and return True/False."""
|
||||
@@ -101,7 +104,8 @@ class Article(models.Model):
|
||||
perms = self.permissions.can_read.all()
|
||||
return perms.count() == 0 or (user in perms)
|
||||
else:
|
||||
return self.parent.can_read(user) if self.parent else True
|
||||
# TODO: We can inherit namespace permissions here
|
||||
return True
|
||||
|
||||
def can_write(self, user):
|
||||
""" Check write permissions and return True/False."""
|
||||
@@ -111,7 +115,8 @@ class Article(models.Model):
|
||||
perms = self.permissions.can_write.all()
|
||||
return perms.count() == 0 or (user in perms)
|
||||
else:
|
||||
return self.parent.can_write(user) if self.parent else True
|
||||
# TODO: We can inherit namespace permissions here
|
||||
return True
|
||||
|
||||
def can_write_l(self, user):
|
||||
"""Check write permissions and locked status"""
|
||||
@@ -123,13 +128,13 @@ class Article(models.Model):
|
||||
return self.can_write_l(user) and (WIKI_ALLOW_ANON_ATTACHMENTS or not user.is_anonymous())
|
||||
|
||||
def __unicode__(self):
|
||||
if self.slug == '' and not self.parent:
|
||||
if self.slug == '':
|
||||
return unicode(_('Root article'))
|
||||
else:
|
||||
return self.get_url()
|
||||
return self.slug
|
||||
|
||||
class Meta:
|
||||
unique_together = (('slug', 'parent'),)
|
||||
unique_together = (('slug', 'namespace'),)
|
||||
verbose_name = _('Article')
|
||||
verbose_name_plural = _('Articles')
|
||||
|
||||
@@ -275,7 +280,7 @@ class Revision(models.Model):
|
||||
|
||||
# Create pre-parsed contents - no need to parse on-the-fly
|
||||
ext = WIKI_MARKDOWN_EXTENSIONS
|
||||
ext += ["wikipath(base_url=%s)" % reverse('wiki_view', args=('/',))]
|
||||
ext += ["wikipath(default_namespace=%s)" % self.article.namespace.name ]
|
||||
self.contents_parsed = markdown(self.contents,
|
||||
extensions=ext,
|
||||
safe_mode='escape',)
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
from django.conf.urls.defaults import *
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
namespace_regex = r"[a-zA-Z\d._-]+"
|
||||
article_slug = r'/(?P<article_path>' + namespace_regex + r'/[a-zA-Z\d_-]*)'
|
||||
namespace = r'/(?P<namespace>' + namespace_regex + r')'
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', 'simplewiki.views.root_redirect', name='wiki_root'),
|
||||
url(r'^view(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.view', name='wiki_view'),
|
||||
url(r'^view_revision/([0-9]*)(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.view_revision', name='wiki_view_revision'),
|
||||
url(r'^edit(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.edit', name='wiki_edit'),
|
||||
url(r'^create(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.create', name='wiki_create'),
|
||||
url(r'^history(/[a-zA-Z\d/_-]*)/([0-9]*)/?$', 'simplewiki.views.history', name='wiki_history'),
|
||||
url(r'^search_related(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.search_add_related', name='search_related'),
|
||||
url(r'^random/?$', 'simplewiki.views.random_article', name='wiki_random'),
|
||||
url(r'^revision_feed/([0-9]*)/?$', 'simplewiki.views.revision_feed', name='wiki_revision_feed'),
|
||||
url(r'^search/?$', 'simplewiki.views.search_articles', name='wiki_search_articles'),
|
||||
url(r'^list/?$', 'simplewiki.views.search_articles', name='wiki_list_articles'), #Just an alias for the search, but you usually don't submit a search term
|
||||
# url(r'^/?([a-zA-Z\d/_-]*)/_related/add/$', 'simplewiki.views.add_related', name='add_related'),
|
||||
# url(r'^/?([a-zA-Z\d/_-]*)/_related/remove/(\d+)$', 'simplewiki.views.remove_related', name='wiki_remove_relation'),
|
||||
# url(r'^/?([a-zA-Z\d/_-]*)/_add_attachment/$', 'simplewiki.views_attachments.add_attachment', name='add_attachment'),
|
||||
# url(r'^/?([a-zA-Z\d/_-]*)/_view_attachment/(.+)?$', 'simplewiki.views_attachments.view_attachment', name='wiki_view_attachment'),
|
||||
# url(r'^(.*)$', 'simplewiki.views.encode_err', name='wiki_encode_err')
|
||||
url(r'^$', 'simplewiki.views.root_redirect', name='wiki_root'),
|
||||
url(r'^view' + article_slug, 'simplewiki.views.view', name='wiki_view'),
|
||||
url(r'^view_revision/(?P<revision_number>[0-9]+)' + article_slug, 'simplewiki.views.view_revision', name='wiki_view_revision'),
|
||||
url(r'^edit' + article_slug, 'simplewiki.views.edit', name='wiki_edit'),
|
||||
url(r'^create' + article_slug, 'simplewiki.views.create', name='wiki_create'),
|
||||
url(r'^history' + article_slug + r'(?:/(?P<page>[0-9]+))?$', 'simplewiki.views.history', name='wiki_history'),
|
||||
url(r'^search_related' + article_slug, 'simplewiki.views.search_add_related', name='search_related'),
|
||||
url(r'^random/?$', 'simplewiki.views.random_article', name='wiki_random'),
|
||||
url(r'^revision_feed' + namespace + r'/(?P<page>[0-9]+)?$', 'simplewiki.views.revision_feed', name='wiki_revision_feed'),
|
||||
url(r'^search' + namespace + r'?$', 'simplewiki.views.search_articles', name='wiki_search_articles'),
|
||||
url(r'^list' + namespace + r'?$', 'simplewiki.views.search_articles', name='wiki_list_articles'), #Just an alias for the search, but you usually don't submit a search term
|
||||
)
|
||||
|
||||
525
lms/djangoapps/simplewiki/views copy.py
Normal file
525
lms/djangoapps/simplewiki/views copy.py
Normal file
@@ -0,0 +1,525 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.conf import settings as settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.context_processors import csrf
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.utils import simplejson
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
|
||||
from models import Revision, Article, CreateArticleForm, RevisionFormWithTitle, RevisionForm
|
||||
import wiki_settings
|
||||
|
||||
def view(request, wiki_url):
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_read=True, check_deleted=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
d = {'wiki_article': article,
|
||||
'wiki_article_revision':article.current_revision,
|
||||
'wiki_write': article.can_write_l(request.user),
|
||||
'wiki_attachments_write': article.can_attach(request.user),
|
||||
'wiki_current_revision_deleted' : not (article.current_revision.deleted == 0),
|
||||
'wiki_title' : article.title + " - MITX 6.002x Wiki"
|
||||
}
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_view.html', d)
|
||||
|
||||
def view_revision(request, revision_number, wiki_url, revision=None):
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
revision = Revision.objects.get(counter=int(revision_number), article=article)
|
||||
except:
|
||||
d = {'wiki_article': article,
|
||||
'wiki_err_norevision': revision_number,}
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_error.html', d)
|
||||
|
||||
|
||||
perm_err = check_permissions(request, article, check_read=True, check_deleted=True, revision=revision)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
d = {'wiki_article': article,
|
||||
'wiki_article_revision':revision,
|
||||
'wiki_write': article.can_write_l(request.user),
|
||||
'wiki_attachments_write': article.can_attach(request.user),
|
||||
'wiki_current_revision_deleted' : not (revision.deleted == 0),
|
||||
}
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_view.html', d)
|
||||
|
||||
|
||||
def root_redirect(request):
|
||||
try:
|
||||
root = Article.get_root()
|
||||
except:
|
||||
err = not_found(request, '/')
|
||||
return err
|
||||
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(root.get_url())))
|
||||
|
||||
def create(request, wiki_url):
|
||||
|
||||
url_path = get_url_path(wiki_url)
|
||||
|
||||
if url_path != [] and url_path[0].startswith('_'):
|
||||
d = {'wiki_err_keyword': True,
|
||||
'wiki_url': '/'.join(url_path) }
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_error.html', d)
|
||||
|
||||
# Lookup path
|
||||
try:
|
||||
# Ensure that the path exists...
|
||||
root = Article.get_root()
|
||||
# Remove root slug if present in path
|
||||
if url_path and root.slug == url_path[0]:
|
||||
url_path = url_path[1:]
|
||||
|
||||
path = Article.get_url_reverse(url_path[:-1], root)
|
||||
if not path:
|
||||
d = {'wiki_err_noparent': True,
|
||||
'wiki_url_parent': '/'.join(url_path[:-1]) }
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_error.html', d)
|
||||
|
||||
perm_err = check_permissions(request, path[-1], check_locked=False, check_write=True, check_deleted=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
# Ensure doesn't already exist
|
||||
article = Article.get_url_reverse(url_path, root)
|
||||
if article:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article[-1].get_url(),)))
|
||||
|
||||
# TODO: Somehow this doesnt work...
|
||||
#except ShouldHaveExactlyOneRootSlug, (e):
|
||||
except:
|
||||
if Article.objects.filter(parent=None).count() > 0:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=('/',)))
|
||||
# Root not found...
|
||||
path = []
|
||||
url_path = [""]
|
||||
|
||||
if request.method == 'POST':
|
||||
f = CreateArticleForm(request.POST)
|
||||
if f.is_valid():
|
||||
article = Article()
|
||||
article.slug = url_path[-1]
|
||||
if not request.user.is_anonymous():
|
||||
article.created_by = request.user
|
||||
article.title = f.cleaned_data.get('title')
|
||||
if path != []:
|
||||
article.parent = path[-1]
|
||||
a = article.save()
|
||||
new_revision = f.save(commit=False)
|
||||
if not request.user.is_anonymous():
|
||||
new_revision.revision_user = request.user
|
||||
new_revision.article = article
|
||||
new_revision.save()
|
||||
import django.db as db
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
else:
|
||||
f = CreateArticleForm(initial={'title':request.GET.get('wiki_article_name', url_path[-1]),
|
||||
'contents':_('Headline\n===\n\n')})
|
||||
|
||||
d = {'wiki_form': f,
|
||||
'wiki_write': True,
|
||||
'create_article' : True,
|
||||
}
|
||||
d.update(csrf(request))
|
||||
|
||||
return render_to_response('simplewiki_edit.html', d)
|
||||
|
||||
def edit(request, wiki_url):
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
# Check write permissions
|
||||
perm_err = check_permissions(request, article, check_write=True, check_locked=True, check_deleted=False)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
if wiki_settings.WIKI_ALLOW_TITLE_EDIT:
|
||||
EditForm = RevisionFormWithTitle
|
||||
else:
|
||||
EditForm = RevisionForm
|
||||
|
||||
if request.method == 'POST':
|
||||
f = EditForm(request.POST)
|
||||
if f.is_valid():
|
||||
new_revision = f.save(commit=False)
|
||||
new_revision.article = article
|
||||
|
||||
if request.POST.__contains__('delete'):
|
||||
if (article.current_revision.deleted == 1): #This article has already been deleted. Redirect
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
new_revision.contents = ""
|
||||
new_revision.deleted = 1
|
||||
elif not new_revision.get_diff():
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
|
||||
if not request.user.is_anonymous():
|
||||
new_revision.revision_user = request.user
|
||||
new_revision.save()
|
||||
if wiki_settings.WIKI_ALLOW_TITLE_EDIT:
|
||||
new_revision.article.title = f.cleaned_data['title']
|
||||
new_revision.article.save()
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
else:
|
||||
startContents = article.current_revision.contents if (article.current_revision.deleted == 0) else 'Headline\n===\n\n'
|
||||
|
||||
f = EditForm({'contents': startContents, 'title': article.title})
|
||||
d = {'wiki_form': f,
|
||||
'wiki_write': True,
|
||||
'wiki_article': article,
|
||||
'wiki_title' : article.title,
|
||||
'wiki_attachments_write': article.can_attach(request.user),
|
||||
'create_article' : False,
|
||||
}
|
||||
d.update(csrf(request))
|
||||
|
||||
return render_to_response('simplewiki_edit.html', d)
|
||||
|
||||
def history(request, wiki_url, page=1):
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_read=True, check_deleted=False)
|
||||
if perm_err:
|
||||
print "returned error " , perm_err
|
||||
return perm_err
|
||||
|
||||
page_size = 10
|
||||
|
||||
try:
|
||||
p = int(page)
|
||||
except ValueError:
|
||||
p = 1
|
||||
|
||||
history = Revision.objects.filter(article__exact = article).order_by('-counter').select_related('previous_revision__counter', 'revision_user', 'wiki_article')
|
||||
|
||||
if request.method == 'POST':
|
||||
if wiki_settings.WIKI_REQUIRE_LOGIN_EDIT and not request.user.is_authenticated():
|
||||
return HttpResponseRedirect('/')
|
||||
|
||||
if request.POST.__contains__('revision'): #They selected a version, but they can be either deleting or changing the version
|
||||
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
redirectURL = reverse('wiki_view', args=(article.get_url(),))
|
||||
try:
|
||||
r = int(request.POST['revision'])
|
||||
revision = Revision.objects.get(id=r)
|
||||
if request.POST.__contains__('change'):
|
||||
article.current_revision = revision
|
||||
article.save()
|
||||
elif request.POST.__contains__('view'):
|
||||
redirectURL = reverse('wiki_view_revision', args=(revision.counter, article.get_url(),))
|
||||
|
||||
#The rese of these are admin functions
|
||||
elif request.POST.__contains__('delete') and request.user.is_superuser:
|
||||
if (revision.deleted == 0):
|
||||
revision.adminSetDeleted(2)
|
||||
elif request.POST.__contains__('restore') and request.user.is_superuser:
|
||||
if (revision.deleted == 2):
|
||||
revision.adminSetDeleted(0)
|
||||
elif request.POST.__contains__('delete_all') and request.user.is_superuser:
|
||||
Revision.objects.filter(article__exact = article, deleted = 0).update(deleted = 2)
|
||||
elif request.POST.__contains__('lock_article'):
|
||||
print "changing locked article " , article.locked
|
||||
article.locked = not article.locked
|
||||
print "changed locked article " , article.locked
|
||||
article.save()
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
return HttpResponseRedirect(redirectURL)
|
||||
#
|
||||
#
|
||||
# <input type="submit" name="delete" value="Delete revision"/>
|
||||
# <input type="submit" name="restore" value="Restore revision"/>
|
||||
# <input type="submit" name="delete_all" value="Delete all revisions">
|
||||
# %else:
|
||||
# <input type="submit" name="delete_article" value="Delete all revisions">
|
||||
#
|
||||
|
||||
page_count = (history.count()+(page_size-1)) / page_size
|
||||
if p > page_count:
|
||||
p = 1
|
||||
beginItem = (p-1) * page_size
|
||||
|
||||
next_page = p + 1 if page_count > p else None
|
||||
prev_page = p - 1 if p > 1 else None
|
||||
|
||||
d = {'wiki_page': p,
|
||||
'wiki_next_page': next_page,
|
||||
'wiki_prev_page': prev_page,
|
||||
'wiki_write': article.can_write_l(request.user),
|
||||
'wiki_attachments_write': article.can_attach(request.user),
|
||||
'wiki_article': article,
|
||||
'wiki_title': article.title,
|
||||
'wiki_history': history[beginItem:beginItem+page_size],
|
||||
'show_delete_revision' : request.user.is_superuser,}
|
||||
d.update(csrf(request))
|
||||
|
||||
return render_to_response('simplewiki_history.html', d)
|
||||
|
||||
|
||||
def revision_feed(request, page=1):
|
||||
page_size = 10
|
||||
|
||||
try:
|
||||
p = int(page)
|
||||
except ValueError:
|
||||
p = 1
|
||||
|
||||
history = Revision.objects.order_by('-revision_date').select_related('revision_user', 'article', 'previous_revision')
|
||||
|
||||
page_count = (history.count()+(page_size-1)) / page_size
|
||||
if p > page_count:
|
||||
p = 1
|
||||
beginItem = (p-1) * page_size
|
||||
|
||||
next_page = p + 1 if page_count > p else None
|
||||
prev_page = p - 1 if p > 1 else None
|
||||
|
||||
d = {'wiki_page': p,
|
||||
'wiki_next_page': next_page,
|
||||
'wiki_prev_page': prev_page,
|
||||
'wiki_history': history[beginItem:beginItem+page_size],
|
||||
'show_delete_revision' : request.user.is_superuser,}
|
||||
d.update(csrf(request))
|
||||
|
||||
return render_to_response('simplewiki_revision_feed.html', d)
|
||||
|
||||
def search_articles(request):
|
||||
# blampe: We should check for the presence of other popular django search
|
||||
# apps and use those if possible. Only fall back on this as a last resort.
|
||||
# Adding some context to results (eg where matches were) would also be nice.
|
||||
|
||||
# todo: maybe do some perm checking here
|
||||
|
||||
if request.method == 'POST':
|
||||
querystring = request.POST['value'].strip()
|
||||
else:
|
||||
querystring = ""
|
||||
|
||||
|
||||
results = Article.objects.all()
|
||||
|
||||
if request.user.is_superuser:
|
||||
results = results.order_by('current_revision__deleted')
|
||||
else:
|
||||
results = results.filter(current_revision__deleted = 0)
|
||||
|
||||
|
||||
if querystring:
|
||||
for queryword in querystring.split():
|
||||
# Basic negation is as fancy as we get right now
|
||||
if queryword[0] == '-' and len(queryword) > 1:
|
||||
results._search = lambda x: results.exclude(x)
|
||||
queryword = queryword[1:]
|
||||
else:
|
||||
results._search = lambda x: results.filter(x)
|
||||
|
||||
results = results._search(Q(current_revision__contents__icontains = queryword) | \
|
||||
Q(title__icontains = queryword))
|
||||
|
||||
results = results.select_related('current_revision__deleted')
|
||||
|
||||
results = sorted(results, key=lambda article: (article.current_revision.deleted, article.get_url().lower()) )
|
||||
|
||||
if len(results) == 1 and querystring:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(results[0].get_url(),)))
|
||||
else:
|
||||
d = {'wiki_search_results': results,
|
||||
'wiki_search_query': querystring,}
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_searchresults.html', d)
|
||||
|
||||
|
||||
def search_add_related(request, wiki_url):
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_read=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
search_string = request.GET.get('query', None)
|
||||
self_pk = request.GET.get('self', None)
|
||||
if search_string:
|
||||
results = []
|
||||
related = Article.objects.filter(title__istartswith = search_string)
|
||||
others = article.related.all()
|
||||
if self_pk:
|
||||
related = related.exclude(pk=self_pk)
|
||||
if others:
|
||||
related = related.exclude(related__in = others)
|
||||
related = related.order_by('title')[:10]
|
||||
for item in related:
|
||||
results.append({'id': str(item.id),
|
||||
'value': item.title,
|
||||
'info': item.get_url()})
|
||||
else:
|
||||
results = []
|
||||
|
||||
json = simplejson.dumps({'results': results})
|
||||
return HttpResponse(json, mimetype='application/json')
|
||||
|
||||
def add_related(request, wiki_url):
|
||||
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
try:
|
||||
related_id = request.POST['id']
|
||||
rel = Article.objects.get(id=related_id)
|
||||
has_already = article.related.filter(id=related_id).count()
|
||||
if has_already == 0 and not rel == article:
|
||||
article.related.add(rel)
|
||||
article.save()
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
|
||||
def remove_related(request, wiki_url, related_id):
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
try:
|
||||
rel_id = int(related_id)
|
||||
rel = Article.objects.get(id=rel_id)
|
||||
article.related.remove(rel)
|
||||
article.save()
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
|
||||
def random_article(request):
|
||||
from random import randint
|
||||
num_arts = Article.objects.count()
|
||||
article = Article.objects.all()[randint(0, num_arts-1)]
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
|
||||
def encode_err(request, url):
|
||||
d = {'wiki_err_encode': True}
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_error.html', d)
|
||||
|
||||
def not_found(request, wiki_url):
|
||||
"""Generate a NOT FOUND message for some URL"""
|
||||
d = {'wiki_err_notfound': True,
|
||||
'wiki_url': wiki_url}
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_error.html', d)
|
||||
|
||||
def get_url_path(url):
|
||||
"""Return a list of all actual elements of a url, safely ignoring
|
||||
double-slashes (//) """
|
||||
return filter(lambda x: x!='', url.split('/'))
|
||||
|
||||
def fetch_from_url(request, url):
|
||||
"""Analyze URL, returning the article and the articles in its path
|
||||
If something goes wrong, return an error HTTP response"""
|
||||
|
||||
err = None
|
||||
article = None
|
||||
path = None
|
||||
|
||||
url_path = get_url_path(url)
|
||||
|
||||
try:
|
||||
root = Article.get_root()
|
||||
except:
|
||||
err = not_found(request, '/')
|
||||
return (article, path, err)
|
||||
|
||||
if url_path and root.slug == url_path[0]:
|
||||
url_path = url_path[1:]
|
||||
|
||||
path = Article.get_url_reverse(url_path, root)
|
||||
if not path:
|
||||
err = not_found(request, '/' + '/'.join(url_path))
|
||||
else:
|
||||
article = path[-1]
|
||||
return (article, path, err)
|
||||
|
||||
|
||||
def check_permissions(request, article, check_read=False, check_write=False, check_locked=False, check_deleted=False, revision = None):
|
||||
read_err = check_read and not article.can_read(request.user)
|
||||
|
||||
write_err = check_write and not article.can_write(request.user)
|
||||
|
||||
locked_err = check_locked and article.locked
|
||||
|
||||
if revision == None:
|
||||
revision = article.current_revision
|
||||
deleted_err = check_deleted and not (revision.deleted == 0)
|
||||
if (request.user.is_superuser):
|
||||
deleted_err = False
|
||||
locked_err = False
|
||||
|
||||
if read_err or write_err or locked_err or deleted_err:
|
||||
d = {'wiki_article': article,
|
||||
'wiki_err_noread': read_err,
|
||||
'wiki_err_nowrite': write_err,
|
||||
'wiki_err_locked': locked_err,
|
||||
'wiki_err_deleted': deleted_err,}
|
||||
d.update(csrf(request))
|
||||
# TODO: Make this a little less jarring by just displaying an error
|
||||
# on the current page? (no such redirect happens for an anon upload yet)
|
||||
# benjaoming: I think this is the nicest way of displaying an error, but
|
||||
# these errors shouldn't occur, but rather be prevented on the other pages.
|
||||
return render_to_response('simplewiki_error.html', d)
|
||||
else:
|
||||
return None
|
||||
|
||||
####################
|
||||
# LOGIN PROTECTION #
|
||||
####################
|
||||
|
||||
if wiki_settings.WIKI_REQUIRE_LOGIN_VIEW:
|
||||
view = login_required(view)
|
||||
history = login_required(history)
|
||||
search_articles = login_required(search_articles)
|
||||
root_redirect = login_required(root_redirect)
|
||||
revision_feed = login_required(revision_feed)
|
||||
random_article = login_required(random_article)
|
||||
search_add_related = login_required(search_add_related)
|
||||
not_found = login_required(not_found)
|
||||
view_revision = login_required(view_revision)
|
||||
|
||||
if wiki_settings.WIKI_REQUIRE_LOGIN_EDIT:
|
||||
create = login_required(create)
|
||||
edit = login_required(edit)
|
||||
add_related = login_required(add_related)
|
||||
remove_related = login_required(remove_related)
|
||||
|
||||
if wiki_settings.WIKI_CONTEXT_PREPROCESSORS:
|
||||
settings.TEMPLATE_CONTEXT_PROCESSORS += wiki_settings.WIKI_CONTEXT_PREPROCESSORS
|
||||
@@ -4,155 +4,178 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.core.context_processors import csrf
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.http import HttpResponse, HttpResponseRedirect, Http404
|
||||
from django.utils import simplejson
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
|
||||
from multicourse import multicourse_settings
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from models import Revision, Article, CreateArticleForm, RevisionFormWithTitle, RevisionForm
|
||||
from models import Revision, Article, Namespace, CreateArticleForm, RevisionFormWithTitle, RevisionForm
|
||||
import wiki_settings
|
||||
|
||||
def view(request, wiki_url):
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
def get_course(course_id):
|
||||
if course_id == None:
|
||||
return None
|
||||
|
||||
course_loc = CourseDescriptor.id_to_location(course_id)
|
||||
course = modulestore().get_item(course_loc)
|
||||
# raise Http404("Course not found")
|
||||
return course
|
||||
|
||||
def wiki_reverse(wiki_page, article = None, course = None, namespace=None, args=[], kwargs={}):
|
||||
kwargs = dict(kwargs) # TODO: Figure out why if I don't do this kwargs sometimes contains {'article_path'}
|
||||
if not 'course_id' in kwargs and course:
|
||||
kwargs['course_id'] = course.id
|
||||
if not 'article_path' in kwargs and article:
|
||||
kwargs['article_path'] = article.get_path()
|
||||
if not 'namespace' in kwargs and namespace:
|
||||
kwargs['namespace'] = namespace
|
||||
return reverse(wiki_page, kwargs=kwargs, args=args)
|
||||
|
||||
def update_template_dictionary(dictionary, request = None, course = None, article = None, revision = None):
|
||||
if article:
|
||||
dictionary['wiki_article'] = article
|
||||
dictionary['wiki_title'] = article.title #TODO: What is the title when viewing the article in a course?
|
||||
if not course and 'namespace' not in dictionary:
|
||||
dictionary['namespace'] = article.namespace.name
|
||||
|
||||
if course:
|
||||
dictionary['course'] = course
|
||||
if 'namespace' not in dictionary:
|
||||
dictionary['namespace'] = course.wiki_namespace
|
||||
else:
|
||||
dictionary['course'] = None
|
||||
|
||||
if revision:
|
||||
dictionary['wiki_article_revision'] = revision
|
||||
dictionary['wiki_current_revision_deleted'] = not (revision.deleted == 0)
|
||||
|
||||
if request:
|
||||
dictionary.update(csrf(request))
|
||||
|
||||
|
||||
def view(request, article_path, course_id=None):
|
||||
course = get_course(course_id)
|
||||
|
||||
(article, err) = get_article(request, article_path, course )
|
||||
if err:
|
||||
return err
|
||||
|
||||
if 'coursename' in request.session: coursename = request.session['coursename']
|
||||
else: coursename = None
|
||||
|
||||
course_number = multicourse_settings.get_course_number(coursename)
|
||||
|
||||
perm_err = check_permissions(request, article, check_read=True, check_deleted=True)
|
||||
perm_err = check_permissions(request, article, course, check_read=True, check_deleted=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
d = {'wiki_article': article,
|
||||
'wiki_article_revision':article.current_revision,
|
||||
'wiki_write': article.can_write_l(request.user),
|
||||
'wiki_attachments_write': article.can_attach(request.user),
|
||||
'wiki_current_revision_deleted' : not (article.current_revision.deleted == 0),
|
||||
'wiki_title' : article.title + " - MITX %s Wiki" % course_number
|
||||
}
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_view.html', d)
|
||||
|
||||
def view_revision(request, revision_number, wiki_url, revision=None):
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
d = {}
|
||||
update_template_dictionary(d, request, course, article, article.current_revision)
|
||||
return render_to_response('simplewiki/simplewiki_view.html', d)
|
||||
|
||||
def view_revision(request, revision_number, article_path, course_id=None):
|
||||
course = get_course(course_id)
|
||||
(article, err) = get_article(request, article_path, course )
|
||||
if err:
|
||||
return err
|
||||
|
||||
try:
|
||||
revision = Revision.objects.get(counter=int(revision_number), article=article)
|
||||
except:
|
||||
d = {'wiki_article': article,
|
||||
'wiki_err_norevision': revision_number,}
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_error.html', d)
|
||||
|
||||
d = {'wiki_err_norevision': revision_number}
|
||||
update_template_dictionary(d, request, course, article)
|
||||
return render_to_response('simplewiki/simplewiki_error.html', d)
|
||||
|
||||
perm_err = check_permissions(request, article, check_read=True, check_deleted=True, revision=revision)
|
||||
perm_err = check_permissions(request, article, course, check_read=True, check_deleted=True, revision=revision)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
d = {'wiki_article': article,
|
||||
'wiki_article_revision':revision,
|
||||
'wiki_write': article.can_write_l(request.user),
|
||||
'wiki_attachments_write': article.can_attach(request.user),
|
||||
'wiki_current_revision_deleted' : not (revision.deleted == 0),
|
||||
}
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_view.html', d)
|
||||
|
||||
d = {}
|
||||
update_template_dictionary(d, request, course, article, revision)
|
||||
|
||||
return render_to_response('simplewiki/simplewiki_view.html', d)
|
||||
|
||||
|
||||
def root_redirect(request):
|
||||
def root_redirect(request, course_id=None):
|
||||
course = get_course(course_id)
|
||||
try:
|
||||
root = Article.get_root()
|
||||
root = Article.get_root(course.wiki_namespace)
|
||||
except:
|
||||
err = not_found(request, '/')
|
||||
# If the root is not found, we probably are loading this class for the first time
|
||||
# We should make sure the namespace exists so the root article can be created.
|
||||
Namespace.ensure_namespace(course.wiki_namespace)
|
||||
|
||||
err = not_found(request, course.wiki_namespace + '/', course)
|
||||
return err
|
||||
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(root.get_url())))
|
||||
|
||||
def create(request, wiki_url):
|
||||
|
||||
url_path = get_url_path(wiki_url)
|
||||
return HttpResponseRedirect(reverse('wiki_view', kwargs={'course_id' : course_id, 'article_path' : root.get_path()} ))
|
||||
|
||||
if url_path != [] and url_path[0].startswith('_'):
|
||||
d = {'wiki_err_keyword': True,
|
||||
'wiki_url': '/'.join(url_path) }
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_error.html', d)
|
||||
def create(request, article_path, course_id=None):
|
||||
course = get_course(course_id)
|
||||
|
||||
article_path_components = article_path.split('/')
|
||||
|
||||
# Lookup path
|
||||
# Ensure the namespace exists
|
||||
if not len(article_path_components) >= 1 or len(article_path_components[0]) == 0:
|
||||
d = {'wiki_err_no_namespace': True}
|
||||
update_template_dictionary(d, request, course)
|
||||
return render_to_response('simplewiki/simplewiki_error.html', d)
|
||||
|
||||
namespace = None
|
||||
try:
|
||||
# Ensure that the path exists...
|
||||
root = Article.get_root()
|
||||
# Remove root slug if present in path
|
||||
if url_path and root.slug == url_path[0]:
|
||||
url_path = url_path[1:]
|
||||
namespace = Namespace.objects.get(name__exact = article_path_components[0])
|
||||
except Namespace.DoesNotExist, ValueError:
|
||||
d = {'wiki_err_bad_namespace': True}
|
||||
update_template_dictionary(d, request, course)
|
||||
return render_to_response('simplewiki/simplewiki_error.html', d)
|
||||
|
||||
path = Article.get_url_reverse(url_path[:-1], root)
|
||||
if not path:
|
||||
d = {'wiki_err_noparent': True,
|
||||
'wiki_url_parent': '/'.join(url_path[:-1]) }
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_error.html', d)
|
||||
|
||||
perm_err = check_permissions(request, path[-1], check_locked=False, check_write=True, check_deleted=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
# Ensure doesn't already exist
|
||||
article = Article.get_url_reverse(url_path, root)
|
||||
if article:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article[-1].get_url(),)))
|
||||
# See if the article already exists
|
||||
article_slug = article_path_components[1] if len(article_path_components) >= 2 else ''
|
||||
#TODO: Make sure the slug only contains legal characters (which is already done a bit by the url regex)
|
||||
|
||||
# TODO: Somehow this doesnt work...
|
||||
#except ShouldHaveExactlyOneRootSlug, (e):
|
||||
except:
|
||||
if Article.objects.filter(parent=None).count() > 0:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=('/',)))
|
||||
# Root not found...
|
||||
path = []
|
||||
url_path = [""]
|
||||
try:
|
||||
existing_article = Article.objects.get(namespace = namespace, slug__exact = article_slug)
|
||||
#It already exists, so we just redirect to view the article
|
||||
return HttpResponseRedirect(wiki_reverse("wiki_view", existing_article, course))
|
||||
except Article.DoesNotExist:
|
||||
#This is good. The article doesn't exist
|
||||
pass
|
||||
|
||||
#TODO: Once we have permissions for namespaces, we should check for create permissions
|
||||
#check_permissions(request, #namespace#, check_locked=False, check_write=True, check_deleted=True)
|
||||
|
||||
if request.method == 'POST':
|
||||
f = CreateArticleForm(request.POST)
|
||||
if f.is_valid():
|
||||
article = Article()
|
||||
article.slug = url_path[-1]
|
||||
article.slug = article_slug
|
||||
if not request.user.is_anonymous():
|
||||
article.created_by = request.user
|
||||
article.title = f.cleaned_data.get('title')
|
||||
if path != []:
|
||||
article.parent = path[-1]
|
||||
article.namespace = namespace
|
||||
a = article.save()
|
||||
new_revision = f.save(commit=False)
|
||||
if not request.user.is_anonymous():
|
||||
new_revision.revision_user = request.user
|
||||
new_revision.article = article
|
||||
new_revision.save()
|
||||
import django.db as db
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
|
||||
return HttpResponseRedirect(wiki_reverse("wiki_view", article, course))
|
||||
else:
|
||||
f = CreateArticleForm(initial={'title':request.GET.get('wiki_article_name', url_path[-1]),
|
||||
f = CreateArticleForm(initial={'title':request.GET.get('wiki_article_name', article_slug),
|
||||
'contents':_('Headline\n===\n\n')})
|
||||
|
||||
d = {'wiki_form': f,
|
||||
'wiki_write': True,
|
||||
'create_article' : True,
|
||||
}
|
||||
d.update(csrf(request))
|
||||
d = {'wiki_form': f, 'create_article' : True, 'namespace' : namespace.name}
|
||||
update_template_dictionary(d, request, course)
|
||||
|
||||
return render_to_response('simplewiki_edit.html', d)
|
||||
return render_to_response('simplewiki/simplewiki_edit.html', d)
|
||||
|
||||
def edit(request, wiki_url):
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
def edit(request, article_path, course_id=None):
|
||||
course = get_course(course_id)
|
||||
(article, err) = get_article(request, article_path, course )
|
||||
if err:
|
||||
return err
|
||||
|
||||
# Check write permissions
|
||||
perm_err = check_permissions(request, article, check_write=True, check_locked=True, check_deleted=False)
|
||||
perm_err = check_permissions(request, article, course, check_write=True, check_locked=True, check_deleted=False)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
@@ -169,11 +192,11 @@ def edit(request, wiki_url):
|
||||
|
||||
if request.POST.__contains__('delete'):
|
||||
if (article.current_revision.deleted == 1): #This article has already been deleted. Redirect
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
return HttpResponseRedirect(wiki_reverse('wiki_view', article, course))
|
||||
new_revision.contents = ""
|
||||
new_revision.deleted = 1
|
||||
elif not new_revision.get_diff():
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
return HttpResponseRedirect(wiki_reverse('wiki_view', article, course))
|
||||
|
||||
if not request.user.is_anonymous():
|
||||
new_revision.revision_user = request.user
|
||||
@@ -181,34 +204,30 @@ def edit(request, wiki_url):
|
||||
if wiki_settings.WIKI_ALLOW_TITLE_EDIT:
|
||||
new_revision.article.title = f.cleaned_data['title']
|
||||
new_revision.article.save()
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
return HttpResponseRedirect(wiki_reverse('wiki_view', article, course))
|
||||
else:
|
||||
startContents = article.current_revision.contents if (article.current_revision.deleted == 0) else 'Headline\n===\n\n'
|
||||
|
||||
f = EditForm({'contents': startContents, 'title': article.title})
|
||||
d = {'wiki_form': f,
|
||||
'wiki_write': True,
|
||||
'wiki_article': article,
|
||||
'wiki_title' : article.title,
|
||||
'wiki_attachments_write': article.can_attach(request.user),
|
||||
'create_article' : False,
|
||||
}
|
||||
d.update(csrf(request))
|
||||
|
||||
d = {'wiki_form': f}
|
||||
update_template_dictionary(d, request, course, article)
|
||||
return render_to_response('simplewiki/simplewiki_edit.html', d)
|
||||
|
||||
return render_to_response('simplewiki_edit.html', d)
|
||||
|
||||
def history(request, wiki_url, page=1):
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
def history(request, article_path, page=1, course_id=None):
|
||||
course = get_course(course_id)
|
||||
(article, err) = get_article(request, article_path, course )
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_read=True, check_deleted=False)
|
||||
perm_err = check_permissions(request, article, course, check_read=True, check_deleted=False)
|
||||
if perm_err:
|
||||
print "returned error " , perm_err
|
||||
return perm_err
|
||||
|
||||
page_size = 10
|
||||
|
||||
if page == None:
|
||||
page = 1
|
||||
try:
|
||||
p = int(page)
|
||||
except ValueError:
|
||||
@@ -218,11 +237,11 @@ def history(request, wiki_url, page=1):
|
||||
|
||||
if request.method == 'POST':
|
||||
if request.POST.__contains__('revision'): #They selected a version, but they can be either deleting or changing the version
|
||||
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
|
||||
perm_err = check_permissions(request, article, course, check_write=True, check_locked=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
redirectURL = reverse('wiki_view', args=(article.get_url(),))
|
||||
redirectURL = wiki_reverse('wiki_view', article, course)
|
||||
try:
|
||||
r = int(request.POST['revision'])
|
||||
revision = Revision.objects.get(id=r)
|
||||
@@ -230,8 +249,8 @@ def history(request, wiki_url, page=1):
|
||||
article.current_revision = revision
|
||||
article.save()
|
||||
elif request.POST.__contains__('view'):
|
||||
redirectURL = reverse('wiki_view_revision', args=(revision.counter, article.get_url(),))
|
||||
|
||||
redirectURL = wiki_reverse('wiki_view_revision', course=course,
|
||||
kwargs={'revision_number' : revision.counter, 'article_path' : article.get_path()})
|
||||
#The rese of these are admin functions
|
||||
elif request.POST.__contains__('delete') and request.user.is_superuser:
|
||||
if (revision.deleted == 0):
|
||||
@@ -242,11 +261,10 @@ def history(request, wiki_url, page=1):
|
||||
elif request.POST.__contains__('delete_all') and request.user.is_superuser:
|
||||
Revision.objects.filter(article__exact = article, deleted = 0).update(deleted = 2)
|
||||
elif request.POST.__contains__('lock_article'):
|
||||
print "changing locked article " , article.locked
|
||||
article.locked = not article.locked
|
||||
print "changed locked article " , article.locked
|
||||
article.save()
|
||||
except:
|
||||
except Exception as e:
|
||||
print str(e)
|
||||
pass
|
||||
finally:
|
||||
return HttpResponseRedirect(redirectURL)
|
||||
@@ -267,23 +285,24 @@ def history(request, wiki_url, page=1):
|
||||
next_page = p + 1 if page_count > p else None
|
||||
prev_page = p - 1 if p > 1 else None
|
||||
|
||||
|
||||
d = {'wiki_page': p,
|
||||
'wiki_next_page': next_page,
|
||||
'wiki_prev_page': prev_page,
|
||||
'wiki_write': article.can_write_l(request.user),
|
||||
'wiki_attachments_write': article.can_attach(request.user),
|
||||
'wiki_article': article,
|
||||
'wiki_title': article.title,
|
||||
'wiki_history': history[beginItem:beginItem+page_size],
|
||||
'show_delete_revision' : request.user.is_superuser,}
|
||||
d.update(csrf(request))
|
||||
|
||||
return render_to_response('simplewiki_history.html', d)
|
||||
'show_delete_revision' : request.user.is_superuser}
|
||||
update_template_dictionary(d, request, course, article)
|
||||
|
||||
return render_to_response('simplewiki/simplewiki_history.html', d)
|
||||
|
||||
|
||||
def revision_feed(request, page=1):
|
||||
def revision_feed(request, page=1, namespace=None, course_id=None):
|
||||
course = get_course(course_id)
|
||||
|
||||
page_size = 10
|
||||
|
||||
if page == None:
|
||||
page = 1
|
||||
try:
|
||||
p = int(page)
|
||||
except ValueError:
|
||||
@@ -303,26 +322,30 @@ def revision_feed(request, page=1):
|
||||
'wiki_next_page': next_page,
|
||||
'wiki_prev_page': prev_page,
|
||||
'wiki_history': history[beginItem:beginItem+page_size],
|
||||
'show_delete_revision' : request.user.is_superuser,}
|
||||
d.update(csrf(request))
|
||||
'show_delete_revision' : request.user.is_superuser,
|
||||
'namespace' : namespace}
|
||||
update_template_dictionary(d, request, course)
|
||||
|
||||
return render_to_response('simplewiki/simplewiki_revision_feed.html', d)
|
||||
|
||||
return render_to_response('simplewiki_revision_feed.html', d)
|
||||
|
||||
def search_articles(request):
|
||||
def search_articles(request, namespace=None, course_id = None):
|
||||
# blampe: We should check for the presence of other popular django search
|
||||
# apps and use those if possible. Only fall back on this as a last resort.
|
||||
# Adding some context to results (eg where matches were) would also be nice.
|
||||
|
||||
# todo: maybe do some perm checking here
|
||||
|
||||
if request.method == 'POST':
|
||||
querystring = request.POST['value'].strip()
|
||||
if request.method == 'GET':
|
||||
querystring = request.GET.get('value', '').strip()
|
||||
else:
|
||||
querystring = ""
|
||||
|
||||
|
||||
results = Article.objects.all()
|
||||
course = get_course(course_id)
|
||||
|
||||
results = Article.objects.all()
|
||||
if namespace:
|
||||
results = results.filter(namespace__name__exact = namespace)
|
||||
|
||||
if request.user.is_superuser:
|
||||
results = results.order_by('current_revision__deleted')
|
||||
else:
|
||||
@@ -341,25 +364,26 @@ def search_articles(request):
|
||||
results = results._search(Q(current_revision__contents__icontains = queryword) | \
|
||||
Q(title__icontains = queryword))
|
||||
|
||||
results = results.select_related('current_revision__deleted')
|
||||
results = results.select_related('current_revision__deleted','namespace')
|
||||
|
||||
results = sorted(results, key=lambda article: (article.current_revision.deleted, article.get_url().lower()) )
|
||||
results = sorted(results, key=lambda article: (article.current_revision.deleted, article.get_path().lower()) )
|
||||
|
||||
if len(results) == 1 and querystring:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(results[0].get_url(),)))
|
||||
return HttpResponseRedirect(wiki_reverse('wiki_view', article=results[0], course=course ))
|
||||
else:
|
||||
d = {'wiki_search_results': results,
|
||||
'wiki_search_query': querystring,}
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_searchresults.html', d)
|
||||
'wiki_search_query': querystring,
|
||||
'namespace' : namespace}
|
||||
update_template_dictionary(d, request, course)
|
||||
return render_to_response('simplewiki/simplewiki_searchresults.html', d)
|
||||
|
||||
|
||||
def search_add_related(request, wiki_url):
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
def search_add_related(request, course_id, slug, namespace):
|
||||
(article, err) = get_article(request, slug, namespace if namespace else course_id )
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_read=True)
|
||||
perm_err = check_permissions(request, article, course, check_read=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
@@ -384,13 +408,12 @@ def search_add_related(request, wiki_url):
|
||||
json = simplejson.dumps({'results': results})
|
||||
return HttpResponse(json, mimetype='application/json')
|
||||
|
||||
def add_related(request, wiki_url):
|
||||
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
def add_related(request, course_id, slug, namespace):
|
||||
(article, err) = get_article(request, slug, namespace if namespace else course_id )
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
|
||||
perm_err = check_permissions(request, article, course, check_write=True, check_locked=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
@@ -406,12 +429,13 @@ def add_related(request, wiki_url):
|
||||
finally:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
|
||||
def remove_related(request, wiki_url, related_id):
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
def remove_related(request, course_id, namespace, slug, related_id):
|
||||
(article, err) = get_article(request, slug, namespace if namespace else course_id )
|
||||
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
|
||||
perm_err = check_permissions(request, article, course, check_write=True, check_locked=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
@@ -425,57 +449,33 @@ def remove_related(request, wiki_url, related_id):
|
||||
finally:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
|
||||
def random_article(request):
|
||||
def random_article(request, course_id):
|
||||
course = get_course(course_id)
|
||||
from random import randint
|
||||
num_arts = Article.objects.count()
|
||||
article = Article.objects.all()[randint(0, num_arts-1)]
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
|
||||
def encode_err(request, url):
|
||||
d = {'wiki_err_encode': True}
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_error.html', d)
|
||||
return HttpResponseRedirect( wiki_reverse('wiki_view', article, course))
|
||||
|
||||
def not_found(request, wiki_url):
|
||||
def not_found(request, article_path, course):
|
||||
"""Generate a NOT FOUND message for some URL"""
|
||||
d = {'wiki_err_notfound': True,
|
||||
'wiki_url': wiki_url}
|
||||
d.update(csrf(request))
|
||||
return render_to_response('simplewiki_error.html', d)
|
||||
|
||||
def get_url_path(url):
|
||||
"""Return a list of all actual elements of a url, safely ignoring
|
||||
double-slashes (//) """
|
||||
return filter(lambda x: x!='', url.split('/'))
|
||||
|
||||
def fetch_from_url(request, url):
|
||||
"""Analyze URL, returning the article and the articles in its path
|
||||
If something goes wrong, return an error HTTP response"""
|
||||
|
||||
'article_path': article_path,
|
||||
'namespace' : course.wiki_namespace}
|
||||
update_template_dictionary(d, request, course)
|
||||
return render_to_response('simplewiki/simplewiki_error.html', d)
|
||||
|
||||
def get_article(request, article_path, course):
|
||||
err = None
|
||||
article = None
|
||||
path = None
|
||||
|
||||
url_path = get_url_path(url)
|
||||
|
||||
try:
|
||||
root = Article.get_root()
|
||||
except:
|
||||
err = not_found(request, '/')
|
||||
return (article, path, err)
|
||||
article = Article.get_article(article_path)
|
||||
except Article.DoesNotExist, ValueError:
|
||||
err = not_found(request, article_path, course)
|
||||
|
||||
return (article, err)
|
||||
|
||||
if url_path and root.slug == url_path[0]:
|
||||
url_path = url_path[1:]
|
||||
|
||||
path = Article.get_url_reverse(url_path, root)
|
||||
if not path:
|
||||
err = not_found(request, '/' + '/'.join(url_path))
|
||||
else:
|
||||
article = path[-1]
|
||||
return (article, path, err)
|
||||
|
||||
|
||||
def check_permissions(request, article, check_read=False, check_write=False, check_locked=False, check_deleted=False, revision = None):
|
||||
def check_permissions(request, article, course, check_read=False, check_write=False, check_locked=False, check_deleted=False, revision = None):
|
||||
read_err = check_read and not article.can_read(request.user)
|
||||
|
||||
write_err = check_write and not article.can_write(request.user)
|
||||
@@ -495,12 +495,12 @@ def check_permissions(request, article, check_read=False, check_write=False, che
|
||||
'wiki_err_nowrite': write_err,
|
||||
'wiki_err_locked': locked_err,
|
||||
'wiki_err_deleted': deleted_err,}
|
||||
d.update(csrf(request))
|
||||
update_template_dictionary(d, request, course)
|
||||
# TODO: Make this a little less jarring by just displaying an error
|
||||
# on the current page? (no such redirect happens for an anon upload yet)
|
||||
# benjaoming: I think this is the nicest way of displaying an error, but
|
||||
# these errors shouldn't occur, but rather be prevented on the other pages.
|
||||
return render_to_response('simplewiki_error.html', d)
|
||||
return render_to_response('simplewiki/simplewiki_error.html', d)
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -508,6 +508,7 @@ def check_permissions(request, article, check_read=False, check_write=False, che
|
||||
# LOGIN PROTECTION #
|
||||
####################
|
||||
|
||||
|
||||
if wiki_settings.WIKI_REQUIRE_LOGIN_VIEW:
|
||||
view = login_required(view)
|
||||
history = login_required(history)
|
||||
|
||||
961
lms/static/js/simplewiki-AutoSuggest_c_2.0.js
Normal file
961
lms/static/js/simplewiki-AutoSuggest_c_2.0.js
Normal file
@@ -0,0 +1,961 @@
|
||||
/**
|
||||
* author: Timothy Groves - http://www.brandspankingnew.net
|
||||
* version: 1.2 - 2006-11-17
|
||||
* 1.3 - 2006-12-04
|
||||
* 2.0 - 2007-02-07
|
||||
*
|
||||
*/
|
||||
|
||||
var useBSNns;
|
||||
|
||||
if (useBSNns)
|
||||
{
|
||||
if (typeof(bsn) == "undefined")
|
||||
bsn = {}
|
||||
_bsn = bsn;
|
||||
}
|
||||
else
|
||||
{
|
||||
_bsn = this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (typeof(_bsn.Autosuggest) == "undefined")
|
||||
_bsn.Autosuggest = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest = function (fldID, param)
|
||||
{
|
||||
// no DOM - give up!
|
||||
//
|
||||
if (!document.getElementById)
|
||||
return false;
|
||||
|
||||
|
||||
|
||||
|
||||
// get field via DOM
|
||||
//
|
||||
this.fld = _bsn.DOM.getElement(fldID);
|
||||
|
||||
if (!this.fld)
|
||||
return false;
|
||||
|
||||
|
||||
|
||||
|
||||
// init variables
|
||||
//
|
||||
this.sInput = "";
|
||||
this.nInputChars = 0;
|
||||
this.aSuggestions = [];
|
||||
this.iHighlighted = 0;
|
||||
|
||||
|
||||
|
||||
|
||||
// parameters object
|
||||
//
|
||||
this.oP = (param) ? param : {};
|
||||
|
||||
// defaults
|
||||
//
|
||||
if (!this.oP.minchars) this.oP.minchars = 1;
|
||||
if (!this.oP.method) this.oP.meth = "get";
|
||||
if (!this.oP.varname) this.oP.varname = "input";
|
||||
if (!this.oP.className) this.oP.className = "autosuggest";
|
||||
if (!this.oP.timeout) this.oP.timeout = 2500;
|
||||
if (!this.oP.delay) this.oP.delay = 500;
|
||||
if (!this.oP.offsety) this.oP.offsety = -5;
|
||||
if (!this.oP.shownoresults) this.oP.shownoresults = true;
|
||||
if (!this.oP.noresults) this.oP.noresults = "No results!";
|
||||
if (!this.oP.maxheight && this.oP.maxheight !== 0) this.oP.maxheight = 250;
|
||||
if (!this.oP.cache && this.oP.cache != false) this.oP.cache = true;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// set keyup handler for field
|
||||
// and prevent autocomplete from client
|
||||
//
|
||||
var pointer = this;
|
||||
|
||||
// NOTE: not using addEventListener because UpArrow fired twice in Safari
|
||||
//_bsn.DOM.addEvent( this.fld, 'keyup', function(ev){ return pointer.onKeyPress(ev); } );
|
||||
|
||||
this.fld.onkeypress = function(ev){ return pointer.onKeyPress(ev); }
|
||||
this.fld.onkeyup = function(ev){ return pointer.onKeyUp(ev); }
|
||||
|
||||
this.fld.setAttribute("autocomplete","off");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.onKeyPress = function(ev)
|
||||
{
|
||||
|
||||
var key = (window.event) ? window.event.keyCode : ev.keyCode;
|
||||
|
||||
|
||||
|
||||
// set responses to keydown events in the field
|
||||
// this allows the user to use the arrow keys to scroll through the results
|
||||
// ESCAPE clears the list
|
||||
// TAB sets the current highlighted value
|
||||
//
|
||||
var RETURN = 13;
|
||||
var TAB = 9;
|
||||
var ESC = 27;
|
||||
|
||||
var bubble = true;
|
||||
|
||||
switch(key)
|
||||
{
|
||||
|
||||
case RETURN:
|
||||
this.setHighlightedValue();
|
||||
bubble = false;
|
||||
break;
|
||||
|
||||
|
||||
case ESC:
|
||||
this.clearSuggestions();
|
||||
break;
|
||||
}
|
||||
|
||||
return bubble;
|
||||
}
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.onKeyUp = function(ev)
|
||||
{
|
||||
var key = (window.event) ? window.event.keyCode : ev.keyCode;
|
||||
|
||||
|
||||
|
||||
// set responses to keydown events in the field
|
||||
// this allows the user to use the arrow keys to scroll through the results
|
||||
// ESCAPE clears the list
|
||||
// TAB sets the current highlighted value
|
||||
//
|
||||
|
||||
var ARRUP = 38;
|
||||
var ARRDN = 40;
|
||||
|
||||
var bubble = true;
|
||||
|
||||
switch(key)
|
||||
{
|
||||
|
||||
|
||||
case ARRUP:
|
||||
this.changeHighlight(key);
|
||||
bubble = false;
|
||||
break;
|
||||
|
||||
|
||||
case ARRDN:
|
||||
this.changeHighlight(key);
|
||||
bubble = false;
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
this.getSuggestions(this.fld.value);
|
||||
}
|
||||
|
||||
return bubble;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.getSuggestions = function (val)
|
||||
{
|
||||
|
||||
// if input stays the same, do nothing
|
||||
//
|
||||
if (val == this.sInput)
|
||||
return false;
|
||||
|
||||
|
||||
// input length is less than the min required to trigger a request
|
||||
// reset input string
|
||||
// do nothing
|
||||
//
|
||||
if (val.length < this.oP.minchars)
|
||||
{
|
||||
this.sInput = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// if caching enabled, and user is typing (ie. length of input is increasing)
|
||||
// filter results out of aSuggestions from last request
|
||||
//
|
||||
if (val.length>this.nInputChars && this.aSuggestions.length && this.oP.cache)
|
||||
{
|
||||
var arr = [];
|
||||
for (var i=0;i<this.aSuggestions.length;i++)
|
||||
{
|
||||
if (this.aSuggestions[i].value.substr(0,val.length).toLowerCase() == val.toLowerCase())
|
||||
arr.push( this.aSuggestions[i] );
|
||||
}
|
||||
|
||||
this.sInput = val;
|
||||
this.nInputChars = val.length;
|
||||
this.aSuggestions = arr;
|
||||
|
||||
this.createList(this.aSuggestions);
|
||||
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
// do new request
|
||||
//
|
||||
{
|
||||
this.sInput = val;
|
||||
this.nInputChars = val.length;
|
||||
|
||||
|
||||
var pointer = this;
|
||||
clearTimeout(this.ajID);
|
||||
this.ajID = setTimeout( function() { pointer.doAjaxRequest() }, this.oP.delay );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.doAjaxRequest = function ()
|
||||
{
|
||||
|
||||
var pointer = this;
|
||||
|
||||
// create ajax request
|
||||
var url = this.oP.script+this.oP.varname+"="+escape(this.fld.value);
|
||||
var meth = this.oP.meth;
|
||||
|
||||
var onSuccessFunc = function (req) { pointer.setSuggestions(req) };
|
||||
var onErrorFunc = function (status) { alert("AJAX error: "+status); };
|
||||
|
||||
var myAjax = new _bsn.Ajax();
|
||||
myAjax.makeRequest( url, meth, onSuccessFunc, onErrorFunc );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.setSuggestions = function (req)
|
||||
{
|
||||
this.aSuggestions = [];
|
||||
|
||||
if (this.oP.json)
|
||||
{
|
||||
var jsondata = eval('(' + req.responseText + ')');
|
||||
|
||||
for (var i=0;i<jsondata.results.length;i++)
|
||||
{
|
||||
this.aSuggestions.push( { 'id':jsondata.results[i].id, 'value':jsondata.results[i].value, 'info':jsondata.results[i].info } );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var xml = req.responseXML;
|
||||
|
||||
// traverse xml
|
||||
//
|
||||
var results = xml.getElementsByTagName('results')[0].childNodes;
|
||||
|
||||
for (var i=0;i<results.length;i++)
|
||||
{
|
||||
if (results[i].hasChildNodes())
|
||||
this.aSuggestions.push( { 'id':results[i].getAttribute('id'), 'value':results[i].childNodes[0].nodeValue, 'info':results[i].getAttribute('info') } );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.idAs = "as_"+this.fld.id;
|
||||
|
||||
|
||||
this.createList(this.aSuggestions);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.createList = function(arr)
|
||||
{
|
||||
var pointer = this;
|
||||
|
||||
|
||||
// get rid of old list
|
||||
// and clear the list removal timeout
|
||||
//
|
||||
_bsn.DOM.removeElement(this.idAs);
|
||||
this.killTimeout();
|
||||
|
||||
|
||||
// create holding div
|
||||
//
|
||||
var div = _bsn.DOM.createElement("div", {id:this.idAs, className:this.oP.className});
|
||||
|
||||
var hcorner = _bsn.DOM.createElement("div", {className:"as_corner"});
|
||||
var hbar = _bsn.DOM.createElement("div", {className:"as_bar"});
|
||||
var header = _bsn.DOM.createElement("div", {className:"as_header"});
|
||||
header.appendChild(hcorner);
|
||||
header.appendChild(hbar);
|
||||
div.appendChild(header);
|
||||
|
||||
|
||||
|
||||
|
||||
// create and populate ul
|
||||
//
|
||||
var ul = _bsn.DOM.createElement("ul", {id:"as_ul"});
|
||||
|
||||
|
||||
|
||||
|
||||
// loop throught arr of suggestions
|
||||
// creating an LI element for each suggestion
|
||||
//
|
||||
for (var i=0;i<arr.length;i++)
|
||||
{
|
||||
// format output with the input enclosed in a EM element
|
||||
// (as HTML, not DOM)
|
||||
//
|
||||
var val = arr[i].value;
|
||||
var st = val.toLowerCase().indexOf( this.sInput.toLowerCase() );
|
||||
var output = val.substring(0,st) + "<em>" + val.substring(st, st+this.sInput.length) + "</em>" + val.substring(st+this.sInput.length);
|
||||
|
||||
|
||||
var span = _bsn.DOM.createElement("span", {}, output, true);
|
||||
if (arr[i].info != "")
|
||||
{
|
||||
var br = _bsn.DOM.createElement("br", {});
|
||||
span.appendChild(br);
|
||||
var small = _bsn.DOM.createElement("small", {}, arr[i].info);
|
||||
span.appendChild(small);
|
||||
}
|
||||
|
||||
var a = _bsn.DOM.createElement("a", { href:"#" });
|
||||
|
||||
var tl = _bsn.DOM.createElement("span", {className:"tl"}, " ");
|
||||
var tr = _bsn.DOM.createElement("span", {className:"tr"}, " ");
|
||||
a.appendChild(tl);
|
||||
a.appendChild(tr);
|
||||
|
||||
a.appendChild(span);
|
||||
|
||||
a.name = i+1;
|
||||
a.onclick = function () { pointer.setHighlightedValue(); return false; }
|
||||
a.onmouseover = function () { pointer.setHighlight(this.name); }
|
||||
|
||||
var li = _bsn.DOM.createElement( "li", {}, a );
|
||||
|
||||
ul.appendChild( li );
|
||||
}
|
||||
|
||||
|
||||
// no results
|
||||
//
|
||||
if (arr.length == 0)
|
||||
{
|
||||
var li = _bsn.DOM.createElement( "li", {className:"as_warning"}, this.oP.noresults );
|
||||
|
||||
ul.appendChild( li );
|
||||
}
|
||||
|
||||
|
||||
div.appendChild( ul );
|
||||
|
||||
|
||||
var fcorner = _bsn.DOM.createElement("div", {className:"as_corner"});
|
||||
var fbar = _bsn.DOM.createElement("div", {className:"as_bar"});
|
||||
var footer = _bsn.DOM.createElement("div", {className:"as_footer"});
|
||||
footer.appendChild(fcorner);
|
||||
footer.appendChild(fbar);
|
||||
div.appendChild(footer);
|
||||
|
||||
|
||||
|
||||
// get position of target textfield
|
||||
// position holding div below it
|
||||
// set width of holding div to width of field
|
||||
//
|
||||
var pos = _bsn.DOM.getPos(this.fld);
|
||||
|
||||
div.style.left = pos.x + "px";
|
||||
div.style.top = ( pos.y + this.fld.offsetHeight + this.oP.offsety ) + "px";
|
||||
div.style.width = this.fld.offsetWidth + "px";
|
||||
|
||||
|
||||
|
||||
// set mouseover functions for div
|
||||
// when mouse pointer leaves div, set a timeout to remove the list after an interval
|
||||
// when mouse enters div, kill the timeout so the list won't be removed
|
||||
//
|
||||
div.onmouseover = function(){ pointer.killTimeout() }
|
||||
div.onmouseout = function(){ pointer.resetTimeout() }
|
||||
|
||||
|
||||
// add DIV to document
|
||||
//
|
||||
document.getElementsByTagName("body")[0].appendChild(div);
|
||||
|
||||
|
||||
|
||||
// currently no item is highlighted
|
||||
//
|
||||
this.iHighlighted = 0;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// remove list after an interval
|
||||
//
|
||||
var pointer = this;
|
||||
this.toID = setTimeout(function () { pointer.clearSuggestions() }, this.oP.timeout);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.changeHighlight = function(key)
|
||||
{
|
||||
var list = _bsn.DOM.getElement("as_ul");
|
||||
if (!list)
|
||||
return false;
|
||||
|
||||
var n;
|
||||
|
||||
if (key == 40)
|
||||
n = this.iHighlighted + 1;
|
||||
else if (key == 38)
|
||||
n = this.iHighlighted - 1;
|
||||
|
||||
|
||||
if (n > list.childNodes.length)
|
||||
n = list.childNodes.length;
|
||||
if (n < 1)
|
||||
n = 1;
|
||||
|
||||
|
||||
this.setHighlight(n);
|
||||
}
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.setHighlight = function(n)
|
||||
{
|
||||
var list = _bsn.DOM.getElement("as_ul");
|
||||
if (!list)
|
||||
return false;
|
||||
|
||||
if (this.iHighlighted > 0)
|
||||
this.clearHighlight();
|
||||
|
||||
this.iHighlighted = Number(n);
|
||||
|
||||
list.childNodes[this.iHighlighted-1].className = "as_highlight";
|
||||
|
||||
|
||||
this.killTimeout();
|
||||
}
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.clearHighlight = function()
|
||||
{
|
||||
var list = _bsn.DOM.getElement("as_ul");
|
||||
if (!list)
|
||||
return false;
|
||||
|
||||
if (this.iHighlighted > 0)
|
||||
{
|
||||
list.childNodes[this.iHighlighted-1].className = "";
|
||||
this.iHighlighted = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.setHighlightedValue = function ()
|
||||
{
|
||||
if (this.iHighlighted)
|
||||
{
|
||||
this.sInput = this.fld.value = this.aSuggestions[ this.iHighlighted-1 ].value;
|
||||
|
||||
// move cursor to end of input (safari)
|
||||
//
|
||||
this.fld.focus();
|
||||
if (this.fld.selectionStart)
|
||||
this.fld.setSelectionRange(this.sInput.length, this.sInput.length);
|
||||
|
||||
|
||||
this.clearSuggestions();
|
||||
|
||||
// pass selected object to callback function, if exists
|
||||
//
|
||||
if (typeof(this.oP.callback) == "function")
|
||||
this.oP.callback( this.aSuggestions[this.iHighlighted-1] );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.killTimeout = function()
|
||||
{
|
||||
clearTimeout(this.toID);
|
||||
}
|
||||
|
||||
_bsn.AutoSuggest.prototype.resetTimeout = function()
|
||||
{
|
||||
clearTimeout(this.toID);
|
||||
var pointer = this;
|
||||
this.toID = setTimeout(function () { pointer.clearSuggestions() }, 1000);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.clearSuggestions = function ()
|
||||
{
|
||||
|
||||
this.killTimeout();
|
||||
|
||||
var ele = _bsn.DOM.getElement(this.idAs);
|
||||
var pointer = this;
|
||||
if (ele)
|
||||
{
|
||||
var fade = new _bsn.Fader(ele,1,0,250,function () { _bsn.DOM.removeElement(pointer.idAs) });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// AJAX PROTOTYPE _____________________________________________
|
||||
|
||||
|
||||
if (typeof(_bsn.Ajax) == "undefined")
|
||||
_bsn.Ajax = {}
|
||||
|
||||
|
||||
|
||||
_bsn.Ajax = function ()
|
||||
{
|
||||
this.req = {};
|
||||
this.isIE = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
_bsn.Ajax.prototype.makeRequest = function (url, meth, onComp, onErr)
|
||||
{
|
||||
|
||||
if (meth != "POST")
|
||||
meth = "GET";
|
||||
|
||||
this.onComplete = onComp;
|
||||
this.onError = onErr;
|
||||
|
||||
var pointer = this;
|
||||
|
||||
// branch for native XMLHttpRequest object
|
||||
if (window.XMLHttpRequest)
|
||||
{
|
||||
this.req = new XMLHttpRequest();
|
||||
this.req.onreadystatechange = function () { pointer.processReqChange() };
|
||||
this.req.open("GET", url, true); //
|
||||
this.req.send(null);
|
||||
// branch for IE/Windows ActiveX version
|
||||
}
|
||||
else if (window.ActiveXObject)
|
||||
{
|
||||
this.req = new ActiveXObject("Microsoft.XMLHTTP");
|
||||
if (this.req)
|
||||
{
|
||||
this.req.onreadystatechange = function () { pointer.processReqChange() };
|
||||
this.req.open(meth, url, true);
|
||||
this.req.send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_bsn.Ajax.prototype.processReqChange = function()
|
||||
{
|
||||
|
||||
// only if req shows "loaded"
|
||||
if (this.req.readyState == 4) {
|
||||
// only if "OK"
|
||||
if (this.req.status == 200)
|
||||
{
|
||||
this.onComplete( this.req );
|
||||
} else {
|
||||
this.onError( this.req.status );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// DOM PROTOTYPE _____________________________________________
|
||||
|
||||
|
||||
if (typeof(_bsn.DOM) == "undefined")
|
||||
_bsn.DOM = {}
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.DOM.createElement = function ( type, attr, cont, html )
|
||||
{
|
||||
var ne = document.createElement( type );
|
||||
if (!ne)
|
||||
return false;
|
||||
|
||||
for (var a in attr)
|
||||
ne[a] = attr[a];
|
||||
|
||||
if (typeof(cont) == "string" && !html)
|
||||
ne.appendChild( document.createTextNode(cont) );
|
||||
else if (typeof(cont) == "string" && html)
|
||||
ne.innerHTML = cont;
|
||||
else if (typeof(cont) == "object")
|
||||
ne.appendChild( cont );
|
||||
|
||||
return ne;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.DOM.clearElement = function ( id )
|
||||
{
|
||||
var ele = this.getElement( id );
|
||||
|
||||
if (!ele)
|
||||
return false;
|
||||
|
||||
while (ele.childNodes.length)
|
||||
ele.removeChild( ele.childNodes[0] );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.DOM.removeElement = function ( ele )
|
||||
{
|
||||
var e = this.getElement(ele);
|
||||
|
||||
if (!e)
|
||||
return false;
|
||||
else if (e.parentNode.removeChild(e))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.DOM.replaceContent = function ( id, cont, html )
|
||||
{
|
||||
var ele = this.getElement( id );
|
||||
|
||||
if (!ele)
|
||||
return false;
|
||||
|
||||
this.clearElement( ele );
|
||||
|
||||
if (typeof(cont) == "string" && !html)
|
||||
ele.appendChild( document.createTextNode(cont) );
|
||||
else if (typeof(cont) == "string" && html)
|
||||
ele.innerHTML = cont;
|
||||
else if (typeof(cont) == "object")
|
||||
ele.appendChild( cont );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.DOM.getElement = function ( ele )
|
||||
{
|
||||
if (typeof(ele) == "undefined")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (typeof(ele) == "string")
|
||||
{
|
||||
var re = document.getElementById( ele );
|
||||
if (!re)
|
||||
return false;
|
||||
else if (typeof(re.appendChild) != "undefined" ) {
|
||||
return re;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (typeof(ele.appendChild) != "undefined")
|
||||
return ele;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.DOM.appendChildren = function ( id, arr )
|
||||
{
|
||||
var ele = this.getElement( id );
|
||||
|
||||
if (!ele)
|
||||
return false;
|
||||
|
||||
|
||||
if (typeof(arr) != "object")
|
||||
return false;
|
||||
|
||||
for (var i=0;i<arr.length;i++)
|
||||
{
|
||||
var cont = arr[i];
|
||||
if (typeof(cont) == "string")
|
||||
ele.appendChild( document.createTextNode(cont) );
|
||||
else if (typeof(cont) == "object")
|
||||
ele.appendChild( cont );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.DOM.getPos = function ( ele )
|
||||
{
|
||||
var ele = this.getElement(ele);
|
||||
|
||||
var obj = ele;
|
||||
|
||||
var curleft = 0;
|
||||
if (obj.offsetParent)
|
||||
{
|
||||
while (obj.offsetParent)
|
||||
{
|
||||
curleft += obj.offsetLeft
|
||||
obj = obj.offsetParent;
|
||||
}
|
||||
}
|
||||
else if (obj.x)
|
||||
curleft += obj.x;
|
||||
|
||||
|
||||
var obj = ele;
|
||||
|
||||
var curtop = 0;
|
||||
if (obj.offsetParent)
|
||||
{
|
||||
while (obj.offsetParent)
|
||||
{
|
||||
curtop += obj.offsetTop
|
||||
obj = obj.offsetParent;
|
||||
}
|
||||
}
|
||||
else if (obj.y)
|
||||
curtop += obj.y;
|
||||
|
||||
return {x:curleft, y:curtop}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// FADER PROTOTYPE _____________________________________________
|
||||
|
||||
|
||||
|
||||
if (typeof(_bsn.Fader) == "undefined")
|
||||
_bsn.Fader = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.Fader = function (ele, from, to, fadetime, callback)
|
||||
{
|
||||
if (!ele)
|
||||
return false;
|
||||
|
||||
this.ele = ele;
|
||||
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
|
||||
this.callback = callback;
|
||||
|
||||
this.nDur = fadetime;
|
||||
|
||||
this.nInt = 50;
|
||||
this.nTime = 0;
|
||||
|
||||
var p = this;
|
||||
this.nID = setInterval(function() { p._fade() }, this.nInt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.Fader.prototype._fade = function()
|
||||
{
|
||||
this.nTime += this.nInt;
|
||||
|
||||
var ieop = Math.round( this._tween(this.nTime, this.from, this.to, this.nDur) * 100 );
|
||||
var op = ieop / 100;
|
||||
|
||||
if (this.ele.filters) // internet explorer
|
||||
{
|
||||
try
|
||||
{
|
||||
this.ele.filters.item("DXImageTransform.Microsoft.Alpha").opacity = ieop;
|
||||
} catch (e) {
|
||||
// If it is not set initially, the browser will throw an error. This will set it if it is not set yet.
|
||||
this.ele.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(opacity='+ieop+')';
|
||||
}
|
||||
}
|
||||
else // other browsers
|
||||
{
|
||||
this.ele.style.opacity = op;
|
||||
}
|
||||
|
||||
|
||||
if (this.nTime == this.nDur)
|
||||
{
|
||||
clearInterval( this.nID );
|
||||
if (this.callback != undefined)
|
||||
this.callback();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
_bsn.Fader.prototype._tween = function(t,b,c,d)
|
||||
{
|
||||
return b + ( (c-b) * (t/d) );
|
||||
}
|
||||
112
lms/static/js/vendor/CodeMirror/codemirror.css
vendored
Normal file
112
lms/static/js/vendor/CodeMirror/codemirror.css
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
.CodeMirror {
|
||||
line-height: 1em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: auto;
|
||||
height: 300px;
|
||||
/* This is needed to prevent an IE[67] bug where the scrolled content
|
||||
is visible outside of the scrolling box. */
|
||||
position: relative;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.CodeMirror-gutter {
|
||||
position: absolute; left: 0; top: 0;
|
||||
z-index: 10;
|
||||
background-color: #f7f7f7;
|
||||
border-right: 1px solid #eee;
|
||||
min-width: 2em;
|
||||
height: 100%;
|
||||
}
|
||||
.CodeMirror-gutter-text {
|
||||
color: #aaa;
|
||||
text-align: right;
|
||||
padding: .4em .2em .4em .4em;
|
||||
white-space: pre !important;
|
||||
}
|
||||
.CodeMirror-lines {
|
||||
padding: .4em;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.CodeMirror pre {
|
||||
-moz-border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
-o-border-radius: 0;
|
||||
border-radius: 0;
|
||||
border-width: 0; margin: 0; padding: 0; background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
padding: 0; margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
.CodeMirror-wrap pre {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.CodeMirror-wrap .CodeMirror-scroll {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror textarea {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.CodeMirror pre.CodeMirror-cursor {
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
border-left: 1px solid black;
|
||||
border-right:none;
|
||||
width:0;
|
||||
}
|
||||
.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
|
||||
.CodeMirror-focused pre.CodeMirror-cursor {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
div.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
|
||||
|
||||
.CodeMirror-searching {
|
||||
background: #ffa;
|
||||
background: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* Default theme */
|
||||
|
||||
.cm-s-default span.cm-keyword {color: #708;}
|
||||
.cm-s-default span.cm-atom {color: #219;}
|
||||
.cm-s-default span.cm-number {color: #164;}
|
||||
.cm-s-default span.cm-def {color: #00f;}
|
||||
.cm-s-default span.cm-variable {color: black;}
|
||||
.cm-s-default span.cm-variable-2 {color: #05a;}
|
||||
.cm-s-default span.cm-variable-3 {color: #085;}
|
||||
.cm-s-default span.cm-property {color: black;}
|
||||
.cm-s-default span.cm-operator {color: black;}
|
||||
.cm-s-default span.cm-comment {color: #a50;}
|
||||
.cm-s-default span.cm-string {color: #a11;}
|
||||
.cm-s-default span.cm-string-2 {color: #f50;}
|
||||
.cm-s-default span.cm-meta {color: #555;}
|
||||
.cm-s-default span.cm-error {color: #f00;}
|
||||
.cm-s-default span.cm-qualifier {color: #555;}
|
||||
.cm-s-default span.cm-builtin {color: #30a;}
|
||||
.cm-s-default span.cm-bracket {color: #cc7;}
|
||||
.cm-s-default span.cm-tag {color: #170;}
|
||||
.cm-s-default span.cm-attribute {color: #00c;}
|
||||
.cm-s-default span.cm-header {color: #a0a;}
|
||||
.cm-s-default span.cm-quote {color: #090;}
|
||||
.cm-s-default span.cm-hr {color: #999;}
|
||||
.cm-s-default span.cm-link {color: #00c;}
|
||||
|
||||
span.cm-header, span.cm-strong {font-weight: bold;}
|
||||
span.cm-em {font-style: italic;}
|
||||
span.cm-emstrong {font-style: italic; font-weight: bold;}
|
||||
span.cm-link {text-decoration: underline;}
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
3026
lms/static/js/vendor/CodeMirror/codemirror.js
vendored
Normal file
3026
lms/static/js/vendor/CodeMirror/codemirror.js
vendored
Normal file
@@ -0,0 +1,3026 @@
|
||||
// All functions that need access to the editor's state live inside
|
||||
// the CodeMirror function. Below that, at the bottom of the file,
|
||||
// some utilities are defined.
|
||||
|
||||
// CodeMirror is the only global var we claim
|
||||
var CodeMirror = (function() {
|
||||
// This is the function that produces an editor instance. Its
|
||||
// closure is used to store the editor state.
|
||||
function CodeMirror(place, givenOptions) {
|
||||
// Determine effective options based on given values and defaults.
|
||||
var options = {}, defaults = CodeMirror.defaults;
|
||||
for (var opt in defaults)
|
||||
if (defaults.hasOwnProperty(opt))
|
||||
options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
|
||||
|
||||
// The element in which the editor lives.
|
||||
var wrapper = document.createElement("div");
|
||||
wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "");
|
||||
// This mess creates the base DOM structure for the editor.
|
||||
wrapper.innerHTML =
|
||||
'<div style="overflow: hidden; position: relative; width: 3px; height: 0px;">' + // Wraps and hides input textarea
|
||||
'<textarea style="position: absolute; padding: 0; width: 1px; height: 1em" wrap="off" ' +
|
||||
'autocorrect="off" autocapitalize="off"></textarea></div>' +
|
||||
'<div class="CodeMirror-scroll" tabindex="-1">' +
|
||||
'<div style="position: relative">' + // Set to the height of the text, causes scrolling
|
||||
'<div style="position: relative">' + // Moved around its parent to cover visible view
|
||||
'<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
|
||||
// Provides positioning relative to (visible) text origin
|
||||
'<div class="CodeMirror-lines"><div style="position: relative; z-index: 0">' +
|
||||
'<div style="position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden;"></div>' +
|
||||
'<pre class="CodeMirror-cursor"> </pre>' + // Absolutely positioned blinky cursor
|
||||
'<div style="position: relative; z-index: -1"></div><div></div>' + // DIVs containing the selection and the actual code
|
||||
'</div></div></div></div></div>';
|
||||
if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
|
||||
// I've never seen more elegant code in my life.
|
||||
var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,
|
||||
scroller = wrapper.lastChild, code = scroller.firstChild,
|
||||
mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,
|
||||
lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,
|
||||
cursor = measure.nextSibling, selectionDiv = cursor.nextSibling,
|
||||
lineDiv = selectionDiv.nextSibling;
|
||||
themeChanged();
|
||||
// Needed to hide big blue blinking cursor on Mobile Safari
|
||||
if (ios) input.style.width = "0px";
|
||||
if (!webkit) lineSpace.draggable = true;
|
||||
lineSpace.style.outline = "none";
|
||||
if (options.tabindex != null) input.tabIndex = options.tabindex;
|
||||
if (options.autofocus) focusInput();
|
||||
if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
|
||||
// Needed to handle Tab key in KHTML
|
||||
if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute";
|
||||
|
||||
// Check for problem with IE innerHTML not working when we have a
|
||||
// P (or similar) parent node.
|
||||
try { stringWidth("x"); }
|
||||
catch (e) {
|
||||
if (e.message.match(/runtime/i))
|
||||
e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)");
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
|
||||
var poll = new Delayed(), highlight = new Delayed(), blinker;
|
||||
|
||||
// mode holds a mode API object. doc is the tree of Line objects,
|
||||
// work an array of lines that should be parsed, and history the
|
||||
// undo history (instance of History constructor).
|
||||
var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused;
|
||||
loadMode();
|
||||
// The selection. These are always maintained to point at valid
|
||||
// positions. Inverted is used to remember that the user is
|
||||
// selecting bottom-to-top.
|
||||
var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
|
||||
// Selection-related flags. shiftSelecting obviously tracks
|
||||
// whether the user is holding shift.
|
||||
var shiftSelecting, lastClick, lastDoubleClick, lastScrollPos = 0, draggingText,
|
||||
overwrite = false, suppressEdits = false;
|
||||
// Variables used by startOperation/endOperation to track what
|
||||
// happened during the operation.
|
||||
var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone,
|
||||
gutterDirty, callbacks;
|
||||
// Current visible range (may be bigger than the view window).
|
||||
var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0;
|
||||
// bracketHighlighted is used to remember that a bracket has been
|
||||
// marked.
|
||||
var bracketHighlighted;
|
||||
// Tracks the maximum line length so that the horizontal scrollbar
|
||||
// can be kept static when scrolling.
|
||||
var maxLine = "", maxWidth;
|
||||
var tabCache = {};
|
||||
|
||||
// Initialize the content.
|
||||
operation(function(){setValue(options.value || ""); updateInput = false;})();
|
||||
var history = new History();
|
||||
|
||||
// Register our event handlers.
|
||||
connect(scroller, "mousedown", operation(onMouseDown));
|
||||
connect(scroller, "dblclick", operation(onDoubleClick));
|
||||
connect(lineSpace, "dragstart", onDragStart);
|
||||
connect(lineSpace, "selectstart", e_preventDefault);
|
||||
// Gecko browsers fire contextmenu *after* opening the menu, at
|
||||
// which point we can't mess with it anymore. Context menu is
|
||||
// handled in onMouseDown for Gecko.
|
||||
if (!gecko) connect(scroller, "contextmenu", onContextMenu);
|
||||
connect(scroller, "scroll", function() {
|
||||
lastScrollPos = scroller.scrollTop;
|
||||
updateDisplay([]);
|
||||
if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
|
||||
if (options.onScroll) options.onScroll(instance);
|
||||
});
|
||||
connect(window, "resize", function() {updateDisplay(true);});
|
||||
connect(input, "keyup", operation(onKeyUp));
|
||||
connect(input, "input", fastPoll);
|
||||
connect(input, "keydown", operation(onKeyDown));
|
||||
connect(input, "keypress", operation(onKeyPress));
|
||||
connect(input, "focus", onFocus);
|
||||
connect(input, "blur", onBlur);
|
||||
|
||||
connect(scroller, "dragenter", e_stop);
|
||||
connect(scroller, "dragover", e_stop);
|
||||
connect(scroller, "drop", operation(onDrop));
|
||||
connect(scroller, "paste", function(){focusInput(); fastPoll();});
|
||||
connect(input, "paste", fastPoll);
|
||||
connect(input, "cut", operation(function(){
|
||||
if (!options.readOnly) replaceSelection("");
|
||||
}));
|
||||
|
||||
// Needed to handle Tab key in KHTML
|
||||
if (khtml) connect(code, "mouseup", function() {
|
||||
if (document.activeElement == input) input.blur();
|
||||
focusInput();
|
||||
});
|
||||
|
||||
// IE throws unspecified error in certain cases, when
|
||||
// trying to access activeElement before onload
|
||||
var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { }
|
||||
if (hasFocus || options.autofocus) setTimeout(onFocus, 20);
|
||||
else onBlur();
|
||||
|
||||
function isLine(l) {return l >= 0 && l < doc.size;}
|
||||
// The instance object that we'll return. Mostly calls out to
|
||||
// local functions in the CodeMirror function. Some do some extra
|
||||
// range checking and/or clipping. operation is used to wrap the
|
||||
// call so that changes it makes are tracked, and the display is
|
||||
// updated afterwards.
|
||||
var instance = wrapper.CodeMirror = {
|
||||
getValue: getValue,
|
||||
setValue: operation(setValue),
|
||||
getSelection: getSelection,
|
||||
replaceSelection: operation(replaceSelection),
|
||||
focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();},
|
||||
setOption: function(option, value) {
|
||||
var oldVal = options[option];
|
||||
options[option] = value;
|
||||
if (option == "mode" || option == "indentUnit") loadMode();
|
||||
else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();}
|
||||
else if (option == "readOnly" && !value) {resetInput(true);}
|
||||
else if (option == "theme") themeChanged();
|
||||
else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
|
||||
else if (option == "tabSize") updateDisplay(true);
|
||||
if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") {
|
||||
gutterChanged();
|
||||
updateDisplay(true);
|
||||
}
|
||||
},
|
||||
getOption: function(option) {return options[option];},
|
||||
undo: operation(undo),
|
||||
redo: operation(redo),
|
||||
indentLine: operation(function(n, dir) {
|
||||
if (typeof dir != "string") {
|
||||
if (dir == null) dir = options.smartIndent ? "smart" : "prev";
|
||||
else dir = dir ? "add" : "subtract";
|
||||
}
|
||||
if (isLine(n)) indentLine(n, dir);
|
||||
}),
|
||||
indentSelection: operation(indentSelected),
|
||||
historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
|
||||
clearHistory: function() {history = new History();},
|
||||
matchBrackets: operation(function(){matchBrackets(true);}),
|
||||
getTokenAt: operation(function(pos) {
|
||||
pos = clipPos(pos);
|
||||
return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch);
|
||||
}),
|
||||
getStateAfter: function(line) {
|
||||
line = clipLine(line == null ? doc.size - 1: line);
|
||||
return getStateBefore(line + 1);
|
||||
},
|
||||
cursorCoords: function(start, mode) {
|
||||
if (start == null) start = sel.inverted;
|
||||
return this.charCoords(start ? sel.from : sel.to, mode);
|
||||
},
|
||||
charCoords: function(pos, mode) {
|
||||
pos = clipPos(pos);
|
||||
if (mode == "local") return localCoords(pos, false);
|
||||
if (mode == "div") return localCoords(pos, true);
|
||||
return pageCoords(pos);
|
||||
},
|
||||
coordsChar: function(coords) {
|
||||
var off = eltOffset(lineSpace);
|
||||
return coordsChar(coords.x - off.left, coords.y - off.top);
|
||||
},
|
||||
markText: operation(markText),
|
||||
setBookmark: setBookmark,
|
||||
findMarksAt: findMarksAt,
|
||||
setMarker: operation(addGutterMarker),
|
||||
clearMarker: operation(removeGutterMarker),
|
||||
setLineClass: operation(setLineClass),
|
||||
hideLine: operation(function(h) {return setLineHidden(h, true);}),
|
||||
showLine: operation(function(h) {return setLineHidden(h, false);}),
|
||||
onDeleteLine: function(line, f) {
|
||||
if (typeof line == "number") {
|
||||
if (!isLine(line)) return null;
|
||||
line = getLine(line);
|
||||
}
|
||||
(line.handlers || (line.handlers = [])).push(f);
|
||||
return line;
|
||||
},
|
||||
lineInfo: lineInfo,
|
||||
addWidget: function(pos, node, scroll, vert, horiz) {
|
||||
pos = localCoords(clipPos(pos));
|
||||
var top = pos.yBot, left = pos.x;
|
||||
node.style.position = "absolute";
|
||||
code.appendChild(node);
|
||||
if (vert == "over") top = pos.y;
|
||||
else if (vert == "near") {
|
||||
var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),
|
||||
hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();
|
||||
if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
|
||||
top = pos.y - node.offsetHeight;
|
||||
if (left + node.offsetWidth > hspace)
|
||||
left = hspace - node.offsetWidth;
|
||||
}
|
||||
node.style.top = (top + paddingTop()) + "px";
|
||||
node.style.left = node.style.right = "";
|
||||
if (horiz == "right") {
|
||||
left = code.clientWidth - node.offsetWidth;
|
||||
node.style.right = "0px";
|
||||
} else {
|
||||
if (horiz == "left") left = 0;
|
||||
else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2;
|
||||
node.style.left = (left + paddingLeft()) + "px";
|
||||
}
|
||||
if (scroll)
|
||||
scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
|
||||
},
|
||||
|
||||
lineCount: function() {return doc.size;},
|
||||
clipPos: clipPos,
|
||||
getCursor: function(start) {
|
||||
if (start == null) start = sel.inverted;
|
||||
return copyPos(start ? sel.from : sel.to);
|
||||
},
|
||||
somethingSelected: function() {return !posEq(sel.from, sel.to);},
|
||||
setCursor: operation(function(line, ch, user) {
|
||||
if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user);
|
||||
else setCursor(line, ch, user);
|
||||
}),
|
||||
setSelection: operation(function(from, to, user) {
|
||||
(user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from));
|
||||
}),
|
||||
getLine: function(line) {if (isLine(line)) return getLine(line).text;},
|
||||
getLineHandle: function(line) {if (isLine(line)) return getLine(line);},
|
||||
setLine: operation(function(line, text) {
|
||||
if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
|
||||
}),
|
||||
removeLine: operation(function(line) {
|
||||
if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
|
||||
}),
|
||||
replaceRange: operation(replaceRange),
|
||||
getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},
|
||||
|
||||
triggerOnKeyDown: operation(onKeyDown),
|
||||
execCommand: function(cmd) {return commands[cmd](instance);},
|
||||
// Stuff used by commands, probably not much use to outside code.
|
||||
moveH: operation(moveH),
|
||||
deleteH: operation(deleteH),
|
||||
moveV: operation(moveV),
|
||||
toggleOverwrite: function() {
|
||||
if(overwrite){
|
||||
overwrite = false;
|
||||
cursor.className = cursor.className.replace(" CodeMirror-overwrite", "");
|
||||
} else {
|
||||
overwrite = true;
|
||||
cursor.className += " CodeMirror-overwrite";
|
||||
}
|
||||
},
|
||||
|
||||
posFromIndex: function(off) {
|
||||
var lineNo = 0, ch;
|
||||
doc.iter(0, doc.size, function(line) {
|
||||
var sz = line.text.length + 1;
|
||||
if (sz > off) { ch = off; return true; }
|
||||
off -= sz;
|
||||
++lineNo;
|
||||
});
|
||||
return clipPos({line: lineNo, ch: ch});
|
||||
},
|
||||
indexFromPos: function (coords) {
|
||||
if (coords.line < 0 || coords.ch < 0) return 0;
|
||||
var index = coords.ch;
|
||||
doc.iter(0, coords.line, function (line) {
|
||||
index += line.text.length + 1;
|
||||
});
|
||||
return index;
|
||||
},
|
||||
scrollTo: function(x, y) {
|
||||
if (x != null) scroller.scrollLeft = x;
|
||||
if (y != null) scroller.scrollTop = y;
|
||||
updateDisplay([]);
|
||||
},
|
||||
|
||||
operation: function(f){return operation(f)();},
|
||||
refresh: function(){
|
||||
updateDisplay(true);
|
||||
if (scroller.scrollHeight > lastScrollPos)
|
||||
scroller.scrollTop = lastScrollPos;
|
||||
},
|
||||
getInputField: function(){return input;},
|
||||
getWrapperElement: function(){return wrapper;},
|
||||
getScrollerElement: function(){return scroller;},
|
||||
getGutterElement: function(){return gutter;}
|
||||
};
|
||||
|
||||
function getLine(n) { return getLineAt(doc, n); }
|
||||
function updateLineHeight(line, height) {
|
||||
gutterDirty = true;
|
||||
var diff = height - line.height;
|
||||
for (var n = line; n; n = n.parent) n.height += diff;
|
||||
}
|
||||
|
||||
function setValue(code) {
|
||||
var top = {line: 0, ch: 0};
|
||||
updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},
|
||||
splitLines(code), top, top);
|
||||
updateInput = true;
|
||||
}
|
||||
function getValue(code) {
|
||||
var text = [];
|
||||
doc.iter(0, doc.size, function(line) { text.push(line.text); });
|
||||
return text.join("\n");
|
||||
}
|
||||
|
||||
function onMouseDown(e) {
|
||||
setShift(e_prop(e, "shiftKey"));
|
||||
// Check whether this is a click in a widget
|
||||
for (var n = e_target(e); n != wrapper; n = n.parentNode)
|
||||
if (n.parentNode == code && n != mover) return;
|
||||
|
||||
// See if this is a click in the gutter
|
||||
for (var n = e_target(e); n != wrapper; n = n.parentNode)
|
||||
if (n.parentNode == gutterText) {
|
||||
if (options.onGutterClick)
|
||||
options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
|
||||
return e_preventDefault(e);
|
||||
}
|
||||
|
||||
var start = posFromMouse(e);
|
||||
|
||||
switch (e_button(e)) {
|
||||
case 3:
|
||||
if (gecko && !mac) onContextMenu(e);
|
||||
return;
|
||||
case 2:
|
||||
if (start) setCursor(start.line, start.ch, true);
|
||||
return;
|
||||
}
|
||||
// For button 1, if it was clicked inside the editor
|
||||
// (posFromMouse returning non-null), we have to adjust the
|
||||
// selection.
|
||||
if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
|
||||
|
||||
if (!focused) onFocus();
|
||||
|
||||
var now = +new Date;
|
||||
if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
|
||||
e_preventDefault(e);
|
||||
setTimeout(focusInput, 20);
|
||||
return selectLine(start.line);
|
||||
} else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
|
||||
lastDoubleClick = {time: now, pos: start};
|
||||
e_preventDefault(e);
|
||||
return selectWordAt(start);
|
||||
} else { lastClick = {time: now, pos: start}; }
|
||||
|
||||
var last = start, going;
|
||||
if (dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) &&
|
||||
!posLess(start, sel.from) && !posLess(sel.to, start)) {
|
||||
// Let the drag handler handle this.
|
||||
if (webkit) lineSpace.draggable = true;
|
||||
var up = connect(document, "mouseup", operation(function(e2) {
|
||||
if (webkit) lineSpace.draggable = false;
|
||||
draggingText = false;
|
||||
up();
|
||||
if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
|
||||
e_preventDefault(e2);
|
||||
setCursor(start.line, start.ch, true);
|
||||
focusInput();
|
||||
}
|
||||
}), true);
|
||||
draggingText = true;
|
||||
// IE's approach to draggable
|
||||
if (lineSpace.dragDrop) lineSpace.dragDrop();
|
||||
return;
|
||||
}
|
||||
e_preventDefault(e);
|
||||
setCursor(start.line, start.ch, true);
|
||||
|
||||
function extend(e) {
|
||||
var cur = posFromMouse(e, true);
|
||||
if (cur && !posEq(cur, last)) {
|
||||
if (!focused) onFocus();
|
||||
last = cur;
|
||||
setSelectionUser(start, cur);
|
||||
updateInput = false;
|
||||
var visible = visibleLines();
|
||||
if (cur.line >= visible.to || cur.line < visible.from)
|
||||
going = setTimeout(operation(function(){extend(e);}), 150);
|
||||
}
|
||||
}
|
||||
|
||||
function done(e) {
|
||||
clearTimeout(going);
|
||||
var cur = posFromMouse(e);
|
||||
if (cur) setSelectionUser(start, cur);
|
||||
e_preventDefault(e);
|
||||
focusInput();
|
||||
updateInput = true;
|
||||
move(); up();
|
||||
}
|
||||
var move = connect(document, "mousemove", operation(function(e) {
|
||||
clearTimeout(going);
|
||||
e_preventDefault(e);
|
||||
if (!ie && !e_button(e)) done(e);
|
||||
else extend(e);
|
||||
}), true);
|
||||
var up = connect(document, "mouseup", operation(done), true);
|
||||
}
|
||||
function onDoubleClick(e) {
|
||||
for (var n = e_target(e); n != wrapper; n = n.parentNode)
|
||||
if (n.parentNode == gutterText) return e_preventDefault(e);
|
||||
var start = posFromMouse(e);
|
||||
if (!start) return;
|
||||
lastDoubleClick = {time: +new Date, pos: start};
|
||||
e_preventDefault(e);
|
||||
selectWordAt(start);
|
||||
}
|
||||
function onDrop(e) {
|
||||
e.preventDefault();
|
||||
var pos = posFromMouse(e, true), files = e.dataTransfer.files;
|
||||
if (!pos || options.readOnly) return;
|
||||
if (files && files.length && window.FileReader && window.File) {
|
||||
function loadFile(file, i) {
|
||||
var reader = new FileReader;
|
||||
reader.onload = function() {
|
||||
text[i] = reader.result;
|
||||
if (++read == n) {
|
||||
pos = clipPos(pos);
|
||||
operation(function() {
|
||||
var end = replaceRange(text.join(""), pos, pos);
|
||||
setSelectionUser(pos, end);
|
||||
})();
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
var n = files.length, text = Array(n), read = 0;
|
||||
for (var i = 0; i < n; ++i) loadFile(files[i], i);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
var text = e.dataTransfer.getData("Text");
|
||||
if (text) {
|
||||
var curFrom = sel.from, curTo = sel.to;
|
||||
setSelectionUser(pos, pos);
|
||||
if (draggingText) replaceRange("", curFrom, curTo);
|
||||
replaceSelection(text);
|
||||
focusInput();
|
||||
}
|
||||
}
|
||||
catch(e){}
|
||||
}
|
||||
}
|
||||
function onDragStart(e) {
|
||||
var txt = getSelection();
|
||||
e.dataTransfer.setData("Text", txt);
|
||||
|
||||
// Use dummy image instead of default browsers image.
|
||||
if (gecko || chrome) {
|
||||
var img = document.createElement('img');
|
||||
img.scr = 'data:image/gif;base64,R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs='; //1x1 image
|
||||
e.dataTransfer.setDragImage(img, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function doHandleBinding(bound, dropShift) {
|
||||
if (typeof bound == "string") {
|
||||
bound = commands[bound];
|
||||
if (!bound) return false;
|
||||
}
|
||||
var prevShift = shiftSelecting;
|
||||
try {
|
||||
if (options.readOnly) suppressEdits = true;
|
||||
if (dropShift) shiftSelecting = null;
|
||||
bound(instance);
|
||||
} catch(e) {
|
||||
if (e != Pass) throw e;
|
||||
return false;
|
||||
} finally {
|
||||
shiftSelecting = prevShift;
|
||||
suppressEdits = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function handleKeyBinding(e) {
|
||||
// Handle auto keymap transitions
|
||||
var startMap = getKeyMap(options.keyMap), next = startMap.auto;
|
||||
clearTimeout(maybeTransition);
|
||||
if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
|
||||
if (getKeyMap(options.keyMap) == startMap) {
|
||||
options.keyMap = (next.call ? next.call(null, instance) : next);
|
||||
}
|
||||
}, 50);
|
||||
|
||||
var name = keyNames[e_prop(e, "keyCode")], handled = false;
|
||||
if (name == null || e.altGraphKey) return false;
|
||||
if (e_prop(e, "altKey")) name = "Alt-" + name;
|
||||
if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name;
|
||||
if (e_prop(e, "metaKey")) name = "Cmd-" + name;
|
||||
|
||||
if (e_prop(e, "shiftKey")) {
|
||||
handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap,
|
||||
function(b) {return doHandleBinding(b, true);})
|
||||
|| lookupKey(name, options.extraKeys, options.keyMap, function(b) {
|
||||
if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b);
|
||||
});
|
||||
} else {
|
||||
handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding);
|
||||
}
|
||||
if (handled) {
|
||||
e_preventDefault(e);
|
||||
if (ie) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
function handleCharBinding(e, ch) {
|
||||
var handled = lookupKey("'" + ch + "'", options.extraKeys,
|
||||
options.keyMap, doHandleBinding);
|
||||
if (handled) e_preventDefault(e);
|
||||
return handled;
|
||||
}
|
||||
|
||||
var lastStoppedKey = null, maybeTransition;
|
||||
function onKeyDown(e) {
|
||||
if (!focused) onFocus();
|
||||
if (ie && e.keyCode == 27) { e.returnValue = false; }
|
||||
if (pollingFast) { if (readInput()) pollingFast = false; }
|
||||
if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
|
||||
var code = e_prop(e, "keyCode");
|
||||
// IE does strange things with escape.
|
||||
setShift(code == 16 || e_prop(e, "shiftKey"));
|
||||
// First give onKeyEvent option a chance to handle this.
|
||||
var handled = handleKeyBinding(e);
|
||||
if (window.opera) {
|
||||
lastStoppedKey = handled ? code : null;
|
||||
// Opera has no cut event... we try to at least catch the key combo
|
||||
if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey"))
|
||||
replaceSelection("");
|
||||
}
|
||||
}
|
||||
function onKeyPress(e) {
|
||||
if (pollingFast) readInput();
|
||||
if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
|
||||
var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
|
||||
if (window.opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
|
||||
if (((window.opera && !e.which) || khtml) && handleKeyBinding(e)) return;
|
||||
var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
|
||||
if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) {
|
||||
if (mode.electricChars.indexOf(ch) > -1)
|
||||
setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75);
|
||||
}
|
||||
if (handleCharBinding(e, ch)) return;
|
||||
fastPoll();
|
||||
}
|
||||
function onKeyUp(e) {
|
||||
if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
|
||||
if (e_prop(e, "keyCode") == 16) shiftSelecting = null;
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
if (options.readOnly == "nocursor") return;
|
||||
if (!focused) {
|
||||
if (options.onFocus) options.onFocus(instance);
|
||||
focused = true;
|
||||
if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
|
||||
wrapper.className += " CodeMirror-focused";
|
||||
if (!leaveInputAlone) resetInput(true);
|
||||
}
|
||||
slowPoll();
|
||||
restartBlink();
|
||||
}
|
||||
function onBlur() {
|
||||
if (focused) {
|
||||
if (options.onBlur) options.onBlur(instance);
|
||||
focused = false;
|
||||
if (bracketHighlighted)
|
||||
operation(function(){
|
||||
if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; }
|
||||
})();
|
||||
wrapper.className = wrapper.className.replace(" CodeMirror-focused", "");
|
||||
}
|
||||
clearInterval(blinker);
|
||||
setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);
|
||||
}
|
||||
|
||||
// Replace the range from from to to by the strings in newText.
|
||||
// Afterwards, set the selection to selFrom, selTo.
|
||||
function updateLines(from, to, newText, selFrom, selTo) {
|
||||
if (suppressEdits) return;
|
||||
|
||||
//This block ensures that widget lines don't have any text inserted on the same line.
|
||||
if (from.ch > 0 && (newText[0] != '' || newText.length == 1) && getLine(from.line).widgetFunction) {
|
||||
newText.unshift('');
|
||||
var widgetLine = getLine(from.line);
|
||||
function moveSel(sel) {
|
||||
if (sel.line == from.line && sel.ch > 0){
|
||||
return {line: sel.line + 1, ch: sel.ch - widgetLine.text.length};
|
||||
} else if (sel.line > from.line) {
|
||||
return {line: sel.line + 1, ch: sel.ch};
|
||||
}
|
||||
}
|
||||
selFrom = moveSel(selFrom, widgetLine);
|
||||
selTo = moveSel(selTo, widgetLine);
|
||||
}
|
||||
if (to.ch == 0 && (newText[newText.length - 1] != '' || newText.length == 1) && getLine(to.line).widgetFunction) {
|
||||
newText.push('');
|
||||
}
|
||||
|
||||
if (history) {
|
||||
var old = [];
|
||||
doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
|
||||
history.addChange(from.line, newText.length, old);
|
||||
while (history.done.length > options.undoDepth) history.done.shift();
|
||||
}
|
||||
updateLinesNoUndo(from, to, newText, selFrom, selTo);
|
||||
}
|
||||
function unredoHelper(from, to) {
|
||||
if (!from.length) return;
|
||||
var set = from.pop(), out = [];
|
||||
for (var i = set.length - 1; i >= 0; i -= 1) {
|
||||
var change = set[i];
|
||||
var replaced = [], end = change.start + change.added;
|
||||
doc.iter(change.start, end, function(line) { replaced.push(line.text); });
|
||||
out.push({start: change.start, added: change.old.length, old: replaced});
|
||||
var pos = clipPos({line: change.start + change.old.length - 1,
|
||||
ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});
|
||||
updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
|
||||
}
|
||||
updateInput = true;
|
||||
to.push(out);
|
||||
}
|
||||
function undo() {unredoHelper(history.done, history.undone);}
|
||||
function redo() {unredoHelper(history.undone, history.done);}
|
||||
|
||||
function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
|
||||
if (suppressEdits) return;
|
||||
var recomputeMaxLength = false, maxLineLength = maxLine.length;
|
||||
if (!options.lineWrapping)
|
||||
doc.iter(from.line, to.line, function(line) {
|
||||
if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
|
||||
});
|
||||
if (from.line != to.line || newText.length > 1) gutterDirty = true;
|
||||
|
||||
var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
|
||||
// First adjust the line structure, taking some care to leave highlighting intact.
|
||||
if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") {
|
||||
// This is a whole-line replace. Treated specially to make
|
||||
// sure line objects move the way they are supposed to.
|
||||
var added = [], prevLine = null;
|
||||
if (from.line) {
|
||||
prevLine = getLine(from.line - 1);
|
||||
prevLine.fixMarkEnds(lastLine);
|
||||
} else lastLine.fixMarkStarts();
|
||||
for (var i = 0, e = newText.length - 1; i < e; ++i)
|
||||
added.push(Line.inheritMarks(newText[i], prevLine));
|
||||
if (nlines) doc.remove(from.line, nlines, callbacks);
|
||||
if (added.length) doc.insert(from.line, added);
|
||||
} else if (firstLine == lastLine) {
|
||||
if (newText.length == 1)
|
||||
firstLine.replace(from.ch, to.ch, newText[0]);
|
||||
else {
|
||||
lastLine = firstLine.split(to.ch, newText[newText.length-1]);
|
||||
firstLine.replace(from.ch, null, newText[0]);
|
||||
firstLine.fixMarkEnds(lastLine);
|
||||
var added = [];
|
||||
for (var i = 1, e = newText.length - 1; i < e; ++i)
|
||||
added.push(Line.inheritMarks(newText[i], firstLine));
|
||||
added.push(lastLine);
|
||||
doc.insert(from.line + 1, added);
|
||||
}
|
||||
} else if (newText.length == 1) {
|
||||
firstLine.replace(from.ch, null, newText[0]);
|
||||
lastLine.replace(null, to.ch, "");
|
||||
firstLine.append(lastLine);
|
||||
doc.remove(from.line + 1, nlines, callbacks);
|
||||
} else {
|
||||
var added = [];
|
||||
firstLine.replace(from.ch, null, newText[0]);
|
||||
lastLine.replace(null, to.ch, newText[newText.length-1]);
|
||||
firstLine.fixMarkEnds(lastLine);
|
||||
for (var i = 1, e = newText.length - 1; i < e; ++i)
|
||||
added.push(Line.inheritMarks(newText[i], firstLine));
|
||||
if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks);
|
||||
doc.insert(from.line + 1, added);
|
||||
}
|
||||
|
||||
// Add these lines to the work array, so that they will be
|
||||
// highlighted. Adjust work lines if lines were added/removed.
|
||||
var newWork = [], lendiff = newText.length - nlines - 1;
|
||||
for (var j = 0, l = work.length; j < l; ++j) {
|
||||
var task = work[j];
|
||||
if (task < from.line) newWork.push(task);
|
||||
else if (task > to.line) newWork.push(task + lendiff);
|
||||
}
|
||||
var hlEnd = from.line + Math.min(newText.length, 500);
|
||||
highlightLines(from.line, hlEnd);
|
||||
newWork.push(hlEnd);
|
||||
work = newWork;
|
||||
startWorker(100);
|
||||
// Remember that these lines changed, for updating the display
|
||||
changes.push({from: from.line, to: to.line + 1, diff: lendiff});
|
||||
var changeObj = {from: from, to: to, text: newText};
|
||||
if (textChanged) {
|
||||
for (var cur = textChanged; cur.next; cur = cur.next) {}
|
||||
cur.next = changeObj;
|
||||
} else textChanged = changeObj;
|
||||
|
||||
|
||||
if (options.lineWrapping) {
|
||||
var perLine = scroller.clientWidth / charWidth() - 3;
|
||||
doc.iter(from.line, from.line + newText.length, function(line) {
|
||||
if (line.hidden) return;
|
||||
var guess = Math.ceil(line.text.length / perLine) || 1;
|
||||
if (line.widgetFunction)
|
||||
guess = line.widgetFunction.size(line.text).height / textHeight();
|
||||
if (guess != line.height) updateLineHeight(line, guess);
|
||||
});
|
||||
} else {
|
||||
doc.iter(from.line, from.line + newText.length, function(line) {
|
||||
var l = line.text;
|
||||
if (l.length > maxLineLength) {
|
||||
maxLine = l; maxLineLength = l.length; maxWidth = null;
|
||||
recomputeMaxLength = false;
|
||||
}
|
||||
if (line.widgetFunction) {
|
||||
var guess = line.widgetFunction.size(line.text).height / textHeight();
|
||||
if (guess != line.height) updateLineHeight(line, guess);
|
||||
} else if (line.height != 1) {
|
||||
updateLineHeight(line, 1);
|
||||
}
|
||||
});
|
||||
if (recomputeMaxLength) {
|
||||
maxLineLength = 0; maxLine = ""; maxWidth = null;
|
||||
doc.iter(0, doc.size, function(line) {
|
||||
var l = line.text;
|
||||
if (l.length > maxLineLength) {
|
||||
maxLineLength = l.length; maxLine = l;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update the selection
|
||||
function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
|
||||
setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line));
|
||||
|
||||
// Make sure the scroll-size div has the correct height.
|
||||
if (scroller.clientHeight)
|
||||
code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px";
|
||||
}
|
||||
|
||||
function replaceRange(code, from, to) {
|
||||
from = clipPos(from);
|
||||
if (!to) to = from; else to = clipPos(to);
|
||||
code = splitLines(code);
|
||||
function adjustPos(pos) {
|
||||
if (posLess(pos, from)) return pos;
|
||||
if (!posLess(to, pos)) return end;
|
||||
var line = pos.line + code.length - (to.line - from.line) - 1;
|
||||
var ch = pos.ch;
|
||||
if (pos.line == to.line)
|
||||
ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0));
|
||||
return {line: line, ch: ch};
|
||||
}
|
||||
var end;
|
||||
replaceRange1(code, from, to, function(end1) {
|
||||
end = end1;
|
||||
return {from: adjustPos(sel.from), to: adjustPos(sel.to)};
|
||||
});
|
||||
return end;
|
||||
}
|
||||
function replaceSelection(code, collapse) {
|
||||
replaceRange1(splitLines(code), sel.from, sel.to, function(end) {
|
||||
if (collapse == "end") return {from: end, to: end};
|
||||
else if (collapse == "start") return {from: sel.from, to: sel.from};
|
||||
else return {from: sel.from, to: end};
|
||||
});
|
||||
}
|
||||
function replaceRange1(code, from, to, computeSel) {
|
||||
var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length;
|
||||
var newSel = computeSel({line: from.line + code.length - 1, ch: endch});
|
||||
updateLines(from, to, code, newSel.from, newSel.to);
|
||||
}
|
||||
|
||||
function getRange(from, to) {
|
||||
var l1 = from.line, l2 = to.line;
|
||||
if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);
|
||||
var code = [getLine(l1).text.slice(from.ch)];
|
||||
doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
|
||||
code.push(getLine(l2).text.slice(0, to.ch));
|
||||
return code.join("\n");
|
||||
}
|
||||
function getSelection() {
|
||||
return getRange(sel.from, sel.to);
|
||||
}
|
||||
|
||||
var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
|
||||
function slowPoll() {
|
||||
if (pollingFast) return;
|
||||
poll.set(options.pollInterval, function() {
|
||||
startOperation();
|
||||
readInput();
|
||||
if (focused) slowPoll();
|
||||
endOperation();
|
||||
});
|
||||
}
|
||||
function fastPoll() {
|
||||
var missed = false;
|
||||
pollingFast = true;
|
||||
function p() {
|
||||
startOperation();
|
||||
var changed = readInput();
|
||||
if (!changed && !missed) {missed = true; poll.set(60, p);}
|
||||
else {pollingFast = false; slowPoll();}
|
||||
endOperation();
|
||||
}
|
||||
poll.set(20, p);
|
||||
}
|
||||
|
||||
// Previnput is a hack to work with IME. If we reset the textarea
|
||||
// on every change, that breaks IME. So we look for changes
|
||||
// compared to the previous content instead. (Modern browsers have
|
||||
// events that indicate IME taking place, but these are not widely
|
||||
// supported or compatible enough yet to rely on.)
|
||||
var prevInput = "";
|
||||
function readInput() {
|
||||
if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false;
|
||||
var text = input.value;
|
||||
if (text == prevInput) return false;
|
||||
shiftSelecting = null;
|
||||
var same = 0, l = Math.min(prevInput.length, text.length);
|
||||
while (same < l && prevInput[same] == text[same]) ++same;
|
||||
if (same < prevInput.length)
|
||||
sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)};
|
||||
else if (overwrite && posEq(sel.from, sel.to))
|
||||
sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))};
|
||||
replaceSelection(text.slice(same), "end");
|
||||
prevInput = text;
|
||||
return true;
|
||||
}
|
||||
function resetInput(user) {
|
||||
if (!posEq(sel.from, sel.to)) {
|
||||
prevInput = "";
|
||||
input.value = getSelection();
|
||||
selectInput(input);
|
||||
} else if (user) prevInput = input.value = "";
|
||||
}
|
||||
|
||||
function focusInput() {
|
||||
if (options.readOnly != "nocursor") input.focus();
|
||||
}
|
||||
|
||||
function scrollEditorIntoView() {
|
||||
if (!cursor.getBoundingClientRect) return;
|
||||
var rect = cursor.getBoundingClientRect();
|
||||
// IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden
|
||||
if (ie && rect.top == rect.bottom) return;
|
||||
var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
|
||||
if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView();
|
||||
}
|
||||
function scrollCursorIntoView() {
|
||||
var cursor = localCoords(sel.inverted ? sel.from : sel.to);
|
||||
var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;
|
||||
return scrollIntoView(x, cursor.y, x, cursor.yBot);
|
||||
}
|
||||
function scrollIntoView(x1, y1, x2, y2) {
|
||||
var pl = paddingLeft(), pt = paddingTop();
|
||||
y1 += pt; y2 += pt; x1 += pl; x2 += pl;
|
||||
var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true;
|
||||
if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1); scrolled = true;}
|
||||
else if (y2 > screentop + screen) {scroller.scrollTop = y2 - screen; scrolled = true;}
|
||||
|
||||
var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
|
||||
var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
|
||||
if (x1 < screenleft + gutterw) {
|
||||
if (x1 < 50) x1 = 0;
|
||||
scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw);
|
||||
scrolled = true;
|
||||
}
|
||||
else if (x2 > screenw + screenleft - 3) {
|
||||
scroller.scrollLeft = x2 + 10 - screenw;
|
||||
scrolled = true;
|
||||
if (x2 > code.clientWidth) result = false;
|
||||
}
|
||||
if (scrolled && options.onScroll) options.onScroll(instance);
|
||||
return result;
|
||||
}
|
||||
|
||||
function visibleLines() {
|
||||
var lh = textHeight(), top = scroller.scrollTop - paddingTop();
|
||||
var from_height = Math.max(0, Math.floor(top / lh));
|
||||
var to_height = Math.ceil((top + scroller.clientHeight) / lh);
|
||||
return {from: lineAtHeight(doc, from_height),
|
||||
to: lineAtHeight(doc, to_height)};
|
||||
}
|
||||
// Uses a set of changes plus the current scroll position to
|
||||
// determine which DOM updates have to be made, and makes the
|
||||
// updates.
|
||||
function updateDisplay(changes, suppressCallback) {
|
||||
if (!scroller.clientWidth) {
|
||||
showingFrom = showingTo = displayOffset = 0;
|
||||
return;
|
||||
}
|
||||
// Compute the new visible window
|
||||
var visible = visibleLines();
|
||||
// Bail out if the visible area is already rendered and nothing changed.
|
||||
if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) return;
|
||||
var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);
|
||||
if (showingFrom < from && from - showingFrom < 20) from = showingFrom;
|
||||
if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);
|
||||
|
||||
// Create a range of theoretically intact lines, and punch holes
|
||||
// in that using the change info.
|
||||
var intact = changes === true ? [] :
|
||||
computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);
|
||||
// Clip off the parts that won't be visible
|
||||
var intactLines = 0;
|
||||
for (var i = 0; i < intact.length; ++i) {
|
||||
var range = intact[i];
|
||||
if (range.from < from) {range.domStart += (from - range.from); range.from = from;}
|
||||
if (range.to > to) range.to = to;
|
||||
if (range.from >= range.to) intact.splice(i--, 1);
|
||||
else intactLines += range.to - range.from;
|
||||
}
|
||||
if (intactLines == to - from) return;
|
||||
intact.sort(function(a, b) {return a.domStart - b.domStart;});
|
||||
|
||||
var th = textHeight(), gutterDisplay = gutter.style.display;
|
||||
lineDiv.style.display = "none";
|
||||
patchDisplay(from, to, intact);
|
||||
lineDiv.style.display = gutter.style.display = "";
|
||||
|
||||
// Position the mover div to align with the lines it's supposed
|
||||
// to be showing (which will cover the visible display)
|
||||
var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th;
|
||||
// This is just a bogus formula that detects when the editor is
|
||||
// resized or the font size changes.
|
||||
if (different) lastSizeC = scroller.clientHeight + th;
|
||||
showingFrom = from; showingTo = to;
|
||||
displayOffset = heightAtLine(doc, from);
|
||||
mover.style.top = (displayOffset * th) + "px";
|
||||
if (scroller.clientHeight)
|
||||
code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
|
||||
|
||||
// Since this is all rather error prone, it is honoured with the
|
||||
// only assertion in the whole file.
|
||||
if (lineDiv.childNodes.length != showingTo - showingFrom)
|
||||
throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) +
|
||||
" nodes=" + lineDiv.childNodes.length);
|
||||
|
||||
if (options.lineWrapping) {
|
||||
maxWidth = scroller.clientWidth;
|
||||
var curNode = lineDiv.firstChild, heightChanged = false;
|
||||
doc.iter(showingFrom, showingTo, function(line) {
|
||||
if (!line.hidden) {
|
||||
var height = Math.round(curNode.offsetHeight / th) || 1;
|
||||
if (line.widgetFunction) height = line.widgetFunction.size(line.text).height / textHeight();
|
||||
if (line.height != height) {
|
||||
updateLineHeight(line, height);
|
||||
gutterDirty = heightChanged = true;
|
||||
}
|
||||
}
|
||||
curNode = curNode.nextSibling;
|
||||
});
|
||||
if (heightChanged)
|
||||
code.style.height = (doc.height * th + 2 * paddingTop()) + "px";
|
||||
} else {
|
||||
if (maxWidth == null) maxWidth = stringWidth(maxLine);
|
||||
if (maxWidth > scroller.clientWidth) {
|
||||
lineSpace.style.width = maxWidth + "px";
|
||||
// Needed to prevent odd wrapping/hiding of widgets placed in here.
|
||||
code.style.width = "";
|
||||
code.style.width = scroller.scrollWidth + "px";
|
||||
} else {
|
||||
lineSpace.style.width = code.style.width = "";
|
||||
}
|
||||
}
|
||||
gutter.style.display = gutterDisplay;
|
||||
if (different || gutterDirty) updateGutter();
|
||||
updateSelection();
|
||||
if (!suppressCallback && options.onUpdate) options.onUpdate(instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
function computeIntact(intact, changes) {
|
||||
for (var i = 0, l = changes.length || 0; i < l; ++i) {
|
||||
var change = changes[i], intact2 = [], diff = change.diff || 0;
|
||||
for (var j = 0, l2 = intact.length; j < l2; ++j) {
|
||||
var range = intact[j];
|
||||
if (change.to <= range.from && change.diff)
|
||||
intact2.push({from: range.from + diff, to: range.to + diff,
|
||||
domStart: range.domStart});
|
||||
else if (change.to <= range.from || change.from >= range.to)
|
||||
intact2.push(range);
|
||||
else {
|
||||
if (change.from > range.from)
|
||||
intact2.push({from: range.from, to: change.from, domStart: range.domStart});
|
||||
if (change.to < range.to)
|
||||
intact2.push({from: change.to + diff, to: range.to + diff,
|
||||
domStart: range.domStart + (change.to - range.from)});
|
||||
}
|
||||
}
|
||||
intact = intact2;
|
||||
}
|
||||
return intact;
|
||||
}
|
||||
|
||||
function patchDisplay(from, to, intact) {
|
||||
// The first pass removes the DOM nodes that aren't intact.
|
||||
if (!intact.length) lineDiv.innerHTML = "";
|
||||
else {
|
||||
function killNode(node) {
|
||||
var tmp = node.nextSibling;
|
||||
node.parentNode.removeChild(node);
|
||||
return tmp;
|
||||
}
|
||||
var domPos = 0, curNode = lineDiv.firstChild, n;
|
||||
for (var i = 0; i < intact.length; ++i) {
|
||||
var cur = intact[i];
|
||||
while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}
|
||||
for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}
|
||||
}
|
||||
while (curNode) curNode = killNode(curNode);
|
||||
}
|
||||
// This pass fills in the lines that actually changed.
|
||||
var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
|
||||
var scratch = document.createElement("div");
|
||||
doc.iter(from, to, function(line) {
|
||||
if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
|
||||
if (!nextIntact || nextIntact.from > j) {
|
||||
if (line.hidden) var html = scratch.innerHTML = "<pre></pre>";
|
||||
else {
|
||||
var html = line.getHTML(makeTab);
|
||||
if (!line.widgetFunction) {
|
||||
html = '<pre' + (line.className ? ' class="' + line.className + '"' : '') + '>' + html + '</pre>';
|
||||
}
|
||||
// Kludge to make sure the styled element lies behind the selection (by z-index)
|
||||
if (line.bgClassName)
|
||||
html = '<div style="position: relative"><pre class="' + line.bgClassName +
|
||||
'" style="position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2"> </pre>' + html + "</div>";
|
||||
}
|
||||
scratch.innerHTML = html;
|
||||
var insertChild = scratch.firstChild;
|
||||
lineDiv.insertBefore(insertChild, curNode);
|
||||
line.nodeAdded(insertChild);
|
||||
} else {
|
||||
curNode = curNode.nextSibling;
|
||||
}
|
||||
++j;
|
||||
});
|
||||
}
|
||||
|
||||
function updateGutter() {
|
||||
if (!options.gutter && !options.lineNumbers) return;
|
||||
var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
|
||||
gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
|
||||
var html = [], i = showingFrom, normalNode;
|
||||
doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
|
||||
if (line.hidden) {
|
||||
html.push("<pre></pre>");
|
||||
} else {
|
||||
var marker = line.gutterMarker;
|
||||
var text = options.lineNumbers ? i + options.firstLineNumber : null;
|
||||
if (marker && marker.text)
|
||||
text = marker.text.replace("%N%", text != null ? text : "");
|
||||
else if (text == null)
|
||||
text = "\u00a0";
|
||||
html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text);
|
||||
for (var j = 1; j < line.height; ++j) html.push("<br/> ");
|
||||
html.push("</pre>");
|
||||
if (!marker) normalNode = i;
|
||||
}
|
||||
++i;
|
||||
});
|
||||
gutter.style.display = "none";
|
||||
gutterText.innerHTML = html.join("");
|
||||
// Make sure scrolling doesn't cause number gutter size to pop
|
||||
if (normalNode != null) {
|
||||
var node = gutterText.childNodes[normalNode - showingFrom];
|
||||
var minwidth = String(doc.size).length, val = eltText(node), pad = "";
|
||||
while (val.length + pad.length < minwidth) pad += "\u00a0";
|
||||
if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild);
|
||||
}
|
||||
gutter.style.display = "";
|
||||
lineSpace.style.marginLeft = gutter.offsetWidth + "px";
|
||||
gutterDirty = false;
|
||||
}
|
||||
function updateSelection() {
|
||||
var collapsed = posEq(sel.from, sel.to);
|
||||
var fromPos = localCoords(sel.from, true);
|
||||
var toPos = collapsed ? fromPos : localCoords(sel.to, true);
|
||||
var headPos = sel.inverted ? fromPos : toPos, th = textHeight();
|
||||
var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);
|
||||
inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px";
|
||||
inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px";
|
||||
if (collapsed) {
|
||||
cursor.style.top = headPos.y + "px";
|
||||
cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px";
|
||||
cursor.style.display = "";
|
||||
selectionDiv.style.display = "none";
|
||||
} else {
|
||||
var sameLine = fromPos.y == toPos.y, html = "";
|
||||
function add(left, top, right, height) {
|
||||
html += '<div class="CodeMirror-selected" style="position: absolute; left: ' + left +
|
||||
'px; top: ' + top + 'px; right: ' + right + 'px; height: ' + height + 'px"></div>';
|
||||
}
|
||||
var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth;
|
||||
var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight;
|
||||
if (sel.from.ch && fromPos.y >= 0) {
|
||||
var right = sameLine ? clientWidth - toPos.x : 0;
|
||||
add(fromPos.x, fromPos.y, right, th);
|
||||
}
|
||||
var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0));
|
||||
var middleHeight = Math.min(toPos.y, clientHeight) - middleStart;
|
||||
if (middleHeight > 0.2 * th)
|
||||
add(0, middleStart, 0, middleHeight);
|
||||
if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th)
|
||||
add(0, toPos.y, clientWidth - toPos.x, th);
|
||||
selectionDiv.innerHTML = html;
|
||||
cursor.style.display = "none";
|
||||
selectionDiv.style.display = "";
|
||||
}
|
||||
}
|
||||
|
||||
function setShift(val) {
|
||||
if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
|
||||
else shiftSelecting = null;
|
||||
}
|
||||
function setSelectionUser(from, to) {
|
||||
var sh = shiftSelecting && clipPos(shiftSelecting);
|
||||
if (sh) {
|
||||
if (posLess(sh, from)) from = sh;
|
||||
else if (posLess(to, sh)) to = sh;
|
||||
}
|
||||
setSelection(from, to);
|
||||
userSelChange = true;
|
||||
}
|
||||
// Update the selection. Last two args are only used by
|
||||
// updateLines, since they have to be expressed in the line
|
||||
// numbers before the update.
|
||||
function setSelection(from, to, oldFrom, oldTo) {
|
||||
goalColumn = null;
|
||||
if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
|
||||
if (posEq(sel.from, from) && posEq(sel.to, to)) return;
|
||||
if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
|
||||
|
||||
// Skip over hidden lines.
|
||||
if (from.line != oldFrom) {
|
||||
var from1 = skipHidden(from, oldFrom, sel.from.ch);
|
||||
// If there is no non-hidden line left, force visibility on current line
|
||||
if (!from1) setLineHidden(from.line, false);
|
||||
else from = from1;
|
||||
}
|
||||
if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);
|
||||
|
||||
if (posEq(from, to)) sel.inverted = false;
|
||||
else if (posEq(from, sel.to)) sel.inverted = false;
|
||||
else if (posEq(to, sel.from)) sel.inverted = true;
|
||||
|
||||
if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) {
|
||||
var head = sel.inverted ? from : to;
|
||||
if (head.line != sel.from.line && sel.from.line < doc.size) {
|
||||
var oldLine = getLine(sel.from.line);
|
||||
if (/^\s+$/.test(oldLine.text))
|
||||
setTimeout(operation(function() {
|
||||
if (oldLine.parent && /^\s+$/.test(oldLine.text)) {
|
||||
var no = lineNo(oldLine);
|
||||
replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length});
|
||||
}
|
||||
}, 10));
|
||||
}
|
||||
}
|
||||
|
||||
sel.from = from; sel.to = to;
|
||||
selectionChanged = true;
|
||||
}
|
||||
function skipHidden(pos, oldLine, oldCh) {
|
||||
function getNonHidden(dir) {
|
||||
var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;
|
||||
while (lNo != end) {
|
||||
var line = getLine(lNo);
|
||||
if (!line.hidden) {
|
||||
var ch = pos.ch;
|
||||
if (ch > oldCh || ch > line.text.length) ch = line.text.length;
|
||||
return {line: lNo, ch: ch};
|
||||
}
|
||||
lNo += dir;
|
||||
}
|
||||
}
|
||||
var line = getLine(pos.line);
|
||||
if (!line.hidden) return pos;
|
||||
if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);
|
||||
else return getNonHidden(-1) || getNonHidden(1);
|
||||
}
|
||||
function setCursor(line, ch, user) {
|
||||
var pos = clipPos({line: line, ch: ch || 0});
|
||||
(user ? setSelectionUser : setSelection)(pos, pos);
|
||||
}
|
||||
|
||||
function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}
|
||||
function clipPos(pos) {
|
||||
if (pos.line < 0) return {line: 0, ch: 0};
|
||||
if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};
|
||||
var ch = pos.ch, line = getLine(pos.line), linelen =line.text.length;
|
||||
if (line.widgetFunction && ch != 0) return {line: pos.line, ch: linelen};
|
||||
if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
|
||||
else if (ch < 0) return {line: pos.line, ch: 0};
|
||||
else return pos;
|
||||
}
|
||||
|
||||
function findPosH(dir, unit) {
|
||||
var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch;
|
||||
var lineObj = getLine(line);
|
||||
function findNextLine() {
|
||||
for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) {
|
||||
var lo = getLine(l);
|
||||
if (!lo.hidden) { line = l; lineObj = lo; return true; }
|
||||
}
|
||||
}
|
||||
function moveOnce(boundToLine) {
|
||||
if (ch == (dir < 0 ? 0 : lineObj.text.length)) {
|
||||
if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0;
|
||||
else return false;
|
||||
} else if (lineObj.widgetFunction) { //Select the entire line
|
||||
ch = dir < 0 ? 0 : lineObj.text.length;
|
||||
} else ch += dir;
|
||||
return true;
|
||||
}
|
||||
if (unit == "char") moveOnce();
|
||||
else if (unit == "column") moveOnce(true);
|
||||
else if (unit == "word") {
|
||||
var sawWord = false;
|
||||
for (;;) {
|
||||
if (dir < 0) if (!moveOnce()) break;
|
||||
if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
|
||||
else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
|
||||
if (dir > 0) if (!moveOnce()) break;
|
||||
}
|
||||
}
|
||||
return {line: line, ch: ch};
|
||||
}
|
||||
function moveH(dir, unit) {
|
||||
var pos = dir < 0 ? sel.from : sel.to;
|
||||
if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit);
|
||||
setCursor(pos.line, pos.ch, true);
|
||||
}
|
||||
function deleteH(dir, unit) {
|
||||
var from = sel.from;
|
||||
var to = sel.to;
|
||||
if (posEq(sel.from, sel.to)) {
|
||||
if (dir < 0) {
|
||||
from = findPosH(dir, unit);
|
||||
if (getLine(from.line).widgetFunction) from.ch = 0;
|
||||
}
|
||||
else {
|
||||
to = findPosH(dir, unit);
|
||||
if (getLine(to.line).widgetFunction) to.ch = getLine(to.line).text.length;
|
||||
}
|
||||
}
|
||||
replaceRange("", from, to);
|
||||
userSelChange = true;
|
||||
}
|
||||
var goalColumn = null;
|
||||
function moveV(dir, unit) {
|
||||
var dist = 0, pos = sel.inverted ? sel.from : sel.to, loc = localCoords(pos, true);
|
||||
if (goalColumn != null) pos.x = goalColumn;
|
||||
if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight);
|
||||
else if (unit == "line") {
|
||||
var line = getLine(pos.line);
|
||||
if (dir > 0 && line.widgetFunction) dist = Math.ceil(getLine(pos.line).height);
|
||||
else dist = 1;
|
||||
dist *= textHeight();
|
||||
}
|
||||
var target = coordsChar(loc.x, loc.y + dist * dir + 2);
|
||||
if (unit == "page") scroller.scrollTop += localCoords(target, true).y - loc.y;
|
||||
setCursor(target.line, target.ch, true);
|
||||
goalColumn = loc.x;
|
||||
}
|
||||
|
||||
function selectWordAt(pos) {
|
||||
var line = getLine(pos.line).text;
|
||||
var start = pos.ch, end = pos.ch;
|
||||
while (start > 0 && isWordChar(line.charAt(start - 1))) --start;
|
||||
while (end < line.length && isWordChar(line.charAt(end))) ++end;
|
||||
setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});
|
||||
}
|
||||
function selectLine(line) {
|
||||
setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0}));
|
||||
}
|
||||
function indentSelected(mode) {
|
||||
if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
|
||||
var e = sel.to.line - (sel.to.ch ? 0 : 1);
|
||||
for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
|
||||
}
|
||||
|
||||
function indentLine(n, how) {
|
||||
if (!how) how = "add";
|
||||
if (how == "smart") {
|
||||
if (!mode.indent) how = "prev";
|
||||
else var state = getStateBefore(n);
|
||||
}
|
||||
|
||||
var line = getLine(n), curSpace = line.indentation(options.tabSize),
|
||||
curSpaceString = line.text.match(/^\s*/)[0], indentation;
|
||||
if (how == "prev") {
|
||||
if (n) indentation = getLine(n-1).indentation(options.tabSize);
|
||||
else indentation = 0;
|
||||
}
|
||||
else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text);
|
||||
else if (how == "add") indentation = curSpace + options.indentUnit;
|
||||
else if (how == "subtract") indentation = curSpace - options.indentUnit;
|
||||
indentation = Math.max(0, indentation);
|
||||
var diff = indentation - curSpace;
|
||||
|
||||
if (!diff) {
|
||||
if (sel.from.line != n && sel.to.line != n) return;
|
||||
var indentString = curSpaceString;
|
||||
}
|
||||
else {
|
||||
var indentString = "", pos = 0;
|
||||
if (options.indentWithTabs)
|
||||
for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";}
|
||||
while (pos < indentation) {++pos; indentString += " ";}
|
||||
}
|
||||
|
||||
replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length});
|
||||
}
|
||||
|
||||
function loadMode() {
|
||||
mode = CodeMirror.getMode(options, options.mode);
|
||||
doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
|
||||
work = [0];
|
||||
startWorker();
|
||||
}
|
||||
function gutterChanged() {
|
||||
var visible = options.gutter || options.lineNumbers;
|
||||
gutter.style.display = visible ? "" : "none";
|
||||
if (visible) gutterDirty = true;
|
||||
else lineDiv.parentNode.style.marginLeft = 0;
|
||||
}
|
||||
function wrappingChanged(from, to) {
|
||||
if (options.lineWrapping) {
|
||||
wrapper.className += " CodeMirror-wrap";
|
||||
var perLine = scroller.clientWidth / charWidth() - 3;
|
||||
doc.iter(0, doc.size, function(line) {
|
||||
if (line.hidden) return;
|
||||
var guess = Math.ceil(line.text.length / perLine) || 1;
|
||||
if (guess != 1) updateLineHeight(line, guess);
|
||||
});
|
||||
lineSpace.style.width = code.style.width = "";
|
||||
} else {
|
||||
wrapper.className = wrapper.className.replace(" CodeMirror-wrap", "");
|
||||
maxWidth = null; maxLine = "";
|
||||
doc.iter(0, doc.size, function(line) {
|
||||
if (line.height != 1 && !line.hidden && !line.widgetFunction) updateLineHeight(line, 1);
|
||||
if (line.text.length > maxLine.length) maxLine = line.text;
|
||||
});
|
||||
}
|
||||
changes.push({from: 0, to: doc.size});
|
||||
}
|
||||
function makeTab(col) {
|
||||
var w = options.tabSize - col % options.tabSize, cached = tabCache[w];
|
||||
if (cached) return cached;
|
||||
for (var str = '<span class="cm-tab">', i = 0; i < w; ++i) str += " ";
|
||||
return (tabCache[w] = {html: str + "</span>", width: w});
|
||||
}
|
||||
function themeChanged() {
|
||||
scroller.className = scroller.className.replace(/\s*cm-s-\w+/g, "") +
|
||||
options.theme.replace(/(^|\s)\s*/g, " cm-s-");
|
||||
}
|
||||
|
||||
function TextMarker() { this.set = []; }
|
||||
TextMarker.prototype.clear = operation(function() {
|
||||
var min = Infinity, max = -Infinity;
|
||||
for (var i = 0, e = this.set.length; i < e; ++i) {
|
||||
var line = this.set[i], mk = line.marked;
|
||||
if (!mk || !line.parent) continue;
|
||||
var lineN = lineNo(line);
|
||||
min = Math.min(min, lineN); max = Math.max(max, lineN);
|
||||
for (var j = 0; j < mk.length; ++j)
|
||||
if (mk[j].marker == this) mk.splice(j--, 1);
|
||||
}
|
||||
if (min != Infinity)
|
||||
changes.push({from: min, to: max + 1});
|
||||
});
|
||||
TextMarker.prototype.find = function() {
|
||||
var from, to;
|
||||
for (var i = 0, e = this.set.length; i < e; ++i) {
|
||||
var line = this.set[i], mk = line.marked;
|
||||
for (var j = 0; j < mk.length; ++j) {
|
||||
var mark = mk[j];
|
||||
if (mark.marker == this) {
|
||||
if (mark.from != null || mark.to != null) {
|
||||
var found = lineNo(line);
|
||||
if (found != null) {
|
||||
if (mark.from != null) from = {line: found, ch: mark.from};
|
||||
if (mark.to != null) to = {line: found, ch: mark.to};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {from: from, to: to};
|
||||
};
|
||||
|
||||
function markText(from, to, className) {
|
||||
from = clipPos(from); to = clipPos(to);
|
||||
var tm = new TextMarker();
|
||||
if (!posLess(from, to)) return tm;
|
||||
function add(line, from, to, className) {
|
||||
getLine(line).addMark(new MarkedText(from, to, className, tm));
|
||||
}
|
||||
if (from.line == to.line) add(from.line, from.ch, to.ch, className);
|
||||
else {
|
||||
add(from.line, from.ch, null, className);
|
||||
for (var i = from.line + 1, e = to.line; i < e; ++i)
|
||||
add(i, null, null, className);
|
||||
add(to.line, null, to.ch, className);
|
||||
}
|
||||
changes.push({from: from.line, to: to.line + 1});
|
||||
return tm;
|
||||
}
|
||||
|
||||
function setBookmark(pos) {
|
||||
pos = clipPos(pos);
|
||||
var bm = new Bookmark(pos.ch);
|
||||
getLine(pos.line).addMark(bm);
|
||||
return bm;
|
||||
}
|
||||
|
||||
function findMarksAt(pos) {
|
||||
pos = clipPos(pos);
|
||||
var markers = [], marked = getLine(pos.line).marked;
|
||||
if (!marked) return markers;
|
||||
for (var i = 0, e = marked.length; i < e; ++i) {
|
||||
var m = marked[i];
|
||||
if ((m.from == null || m.from <= pos.ch) &&
|
||||
(m.to == null || m.to >= pos.ch))
|
||||
markers.push(m.marker || m);
|
||||
}
|
||||
return markers;
|
||||
}
|
||||
|
||||
function addGutterMarker(line, text, className) {
|
||||
if (typeof line == "number") line = getLine(clipLine(line));
|
||||
line.gutterMarker = {text: text, style: className};
|
||||
gutterDirty = true;
|
||||
return line;
|
||||
}
|
||||
function removeGutterMarker(line) {
|
||||
if (typeof line == "number") line = getLine(clipLine(line));
|
||||
line.gutterMarker = null;
|
||||
gutterDirty = true;
|
||||
}
|
||||
|
||||
function changeLine(handle, op) {
|
||||
var no = handle, line = handle;
|
||||
if (typeof handle == "number") line = getLine(clipLine(handle));
|
||||
else no = lineNo(handle);
|
||||
if (no == null) return null;
|
||||
if (op(line, no)) changes.push({from: no, to: no + 1});
|
||||
else return null;
|
||||
return line;
|
||||
}
|
||||
function setLineClass(handle, className, bgClassName) {
|
||||
return changeLine(handle, function(line) {
|
||||
if (line.className != className || line.bgClassName != bgClassName) {
|
||||
line.className = className;
|
||||
line.bgClassName = bgClassName;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
function setLineHidden(handle, hidden) {
|
||||
return changeLine(handle, function(line, no) {
|
||||
if (line.hidden != hidden) {
|
||||
line.hidden = hidden;
|
||||
updateLineHeight(line, hidden ? 0 : 1);
|
||||
var fline = sel.from.line, tline = sel.to.line;
|
||||
if (hidden && (fline == no || tline == no)) {
|
||||
var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from;
|
||||
var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to;
|
||||
// Can't hide the last visible line, we'd have no place to put the cursor
|
||||
if (!to) return;
|
||||
setSelection(from, to);
|
||||
}
|
||||
return (gutterDirty = true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function lineInfo(line) {
|
||||
if (typeof line == "number") {
|
||||
if (!isLine(line)) return null;
|
||||
var n = line;
|
||||
line = getLine(line);
|
||||
if (!line) return null;
|
||||
}
|
||||
else {
|
||||
var n = lineNo(line);
|
||||
if (n == null) return null;
|
||||
}
|
||||
var marker = line.gutterMarker;
|
||||
return {line: n, handle: line, text: line.text, markerText: marker && marker.text,
|
||||
markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName};
|
||||
}
|
||||
|
||||
function stringWidth(str) {
|
||||
measure.innerHTML = "<pre><span>x</span></pre>";
|
||||
measure.firstChild.firstChild.firstChild.nodeValue = str;
|
||||
return measure.firstChild.firstChild.offsetWidth || 10;
|
||||
}
|
||||
// These are used to go from pixel positions to character
|
||||
// positions, taking varying character widths into account.
|
||||
function charFromX(line, x) {
|
||||
if (x <= 0) return 0;
|
||||
var lineObj = getLine(line), text = lineObj.text;
|
||||
function getX(len) {
|
||||
measure.innerHTML = "<pre><span>" + lineObj.getHTML(makeTab, len) + "</span></pre>";
|
||||
return measure.firstChild.firstChild.offsetWidth;
|
||||
}
|
||||
var from = 0, fromX = 0, to = text.length, toX;
|
||||
// Guess a suitable upper bound for our search.
|
||||
var estimated = Math.min(to, Math.ceil(x / charWidth()));
|
||||
for (;;) {
|
||||
var estX = getX(estimated);
|
||||
if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
|
||||
else {toX = estX; to = estimated; break;}
|
||||
}
|
||||
if (x > toX) return to;
|
||||
// Try to guess a suitable lower bound as well.
|
||||
estimated = Math.floor(to * 0.8); estX = getX(estimated);
|
||||
if (estX < x) {from = estimated; fromX = estX;}
|
||||
// Do a binary search between these bounds.
|
||||
for (;;) {
|
||||
if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
|
||||
var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
|
||||
if (middleX > x) {to = middle; toX = middleX;}
|
||||
else {from = middle; fromX = middleX;}
|
||||
}
|
||||
}
|
||||
|
||||
var tempId = Math.floor(Math.random() * 0xffffff).toString(16);
|
||||
function measureLine(line, ch) {
|
||||
if (ch == 0) return {top: 0, left: 0};
|
||||
if (line.widgetFunction) {
|
||||
var size = line.widgetFunction.size(line.text);
|
||||
return {top: -1, left: size.width};
|
||||
}
|
||||
var extra = "";
|
||||
// Include extra text at the end to make sure the measured line is wrapped in the right way.
|
||||
if (options.lineWrapping) {
|
||||
var end = line.text.indexOf(" ", ch + 6);
|
||||
extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0)));
|
||||
}
|
||||
measure.innerHTML = "<pre>" + line.getHTML(makeTab, ch) +
|
||||
'<span id="CodeMirror-temp-' + tempId + '">' + htmlEscape(line.text.charAt(ch) || " ") + "</span>" +
|
||||
extra + "</pre>";
|
||||
var elt = document.getElementById("CodeMirror-temp-" + tempId);
|
||||
var top = elt.offsetTop, left = elt.offsetLeft;
|
||||
// Older IEs report zero offsets for spans directly after a wrap
|
||||
if (ie && top == 0 && left == 0) {
|
||||
var backup = document.createElement("span");
|
||||
backup.innerHTML = "x";
|
||||
elt.parentNode.insertBefore(backup, elt.nextSibling);
|
||||
top = backup.offsetTop;
|
||||
}
|
||||
return {top: top, left: left};
|
||||
}
|
||||
function localCoords(pos, inLineWrap) {
|
||||
var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));
|
||||
if (pos.ch == 0) x = 0;
|
||||
else {
|
||||
var sp = measureLine(getLine(pos.line), pos.ch);
|
||||
x = sp.left;
|
||||
if (options.lineWrapping) y += Math.max(0, sp.top);
|
||||
}
|
||||
return {x: x, y: y, yBot: y + lh};
|
||||
}
|
||||
// Coords must be lineSpace-local
|
||||
function coordsChar(x, y) {
|
||||
if (y < 0) y = 0;
|
||||
var th = textHeight(), cw = charWidth(), heightPos = displayOffset + y / th;
|
||||
var lineNo = lineAtHeight(doc, heightPos);
|
||||
if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length};
|
||||
var lineObj = getLine(lineNo), text = lineObj.text;
|
||||
var tw = options.lineWrapping, innerOff = tw ? Math.floor(heightPos - heightAtLine(doc, lineNo)) : 0;
|
||||
if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};
|
||||
function getX(len) {
|
||||
var sp = measureLine(lineObj, len);
|
||||
if (tw) {
|
||||
var off = Math.round(sp.top / th);
|
||||
return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);
|
||||
}
|
||||
return sp.left;
|
||||
}
|
||||
var from = 0, fromX = 0, to = text.length, toX;
|
||||
// Guess a suitable upper bound for our search.
|
||||
var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw));
|
||||
for (;;) {
|
||||
var estX = getX(estimated);
|
||||
if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
|
||||
else {toX = estX; to = estimated; break;}
|
||||
}
|
||||
if (x > toX) return {line: lineNo, ch: to};
|
||||
// Try to guess a suitable lower bound as well.
|
||||
estimated = Math.floor(to * 0.8); estX = getX(estimated);
|
||||
if (estX < x) {from = estimated; fromX = estX;}
|
||||
// Do a binary search between these bounds.
|
||||
for (;;) {
|
||||
if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to};
|
||||
var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
|
||||
if (middleX > x) {to = middle; toX = middleX;}
|
||||
else {from = middle; fromX = middleX;}
|
||||
}
|
||||
}
|
||||
function pageCoords(pos) {
|
||||
var local = localCoords(pos, true), off = eltOffset(lineSpace);
|
||||
return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
|
||||
}
|
||||
|
||||
var cachedHeight, cachedHeightFor, measureText;
|
||||
function textHeight() {
|
||||
if (measureText == null) {
|
||||
measureText = "<pre>";
|
||||
for (var i = 0; i < 49; ++i) measureText += "x<br/>";
|
||||
measureText += "x</pre>";
|
||||
}
|
||||
var offsetHeight = lineDiv.clientHeight;
|
||||
if (offsetHeight == cachedHeightFor) return cachedHeight;
|
||||
cachedHeightFor = offsetHeight;
|
||||
measure.innerHTML = measureText;
|
||||
cachedHeight = measure.firstChild.offsetHeight / 50 || 1;
|
||||
measure.innerHTML = "";
|
||||
return cachedHeight;
|
||||
}
|
||||
var cachedWidth, cachedWidthFor = 0;
|
||||
function charWidth() {
|
||||
if (scroller.clientWidth == cachedWidthFor) return cachedWidth;
|
||||
cachedWidthFor = scroller.clientWidth;
|
||||
return (cachedWidth = stringWidth("x"));
|
||||
}
|
||||
function paddingTop() {return lineSpace.offsetTop;}
|
||||
function paddingLeft() {return lineSpace.offsetLeft;}
|
||||
|
||||
function posFromMouse(e, liberal) {
|
||||
var offW = eltOffset(scroller, true), x, y;
|
||||
// Fails unpredictably on IE[67] when mouse is dragged around quickly.
|
||||
try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
|
||||
// This is a mess of a heuristic to try and determine whether a
|
||||
// scroll-bar was clicked or not, and to return null if one was
|
||||
// (and !liberal).
|
||||
if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
|
||||
return null;
|
||||
var offL = eltOffset(lineSpace, true);
|
||||
return coordsChar(x - offL.left, y - offL.top);
|
||||
}
|
||||
function onContextMenu(e) {
|
||||
var pos = posFromMouse(e), scrollPos = scroller.scrollTop;
|
||||
if (!pos || window.opera) return; // Opera is difficult.
|
||||
if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
|
||||
operation(setCursor)(pos.line, pos.ch);
|
||||
|
||||
var oldCSS = input.style.cssText;
|
||||
inputDiv.style.position = "absolute";
|
||||
input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
|
||||
"px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " +
|
||||
"border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
|
||||
leaveInputAlone = true;
|
||||
var val = input.value = getSelection();
|
||||
focusInput();
|
||||
selectInput(input);
|
||||
function rehide() {
|
||||
var newVal = splitLines(input.value).join("\n");
|
||||
if (newVal != val) operation(replaceSelection)(newVal, "end");
|
||||
inputDiv.style.position = "relative";
|
||||
input.style.cssText = oldCSS;
|
||||
if (ie_lt9) scroller.scrollTop = scrollPos;
|
||||
leaveInputAlone = false;
|
||||
resetInput(true);
|
||||
slowPoll();
|
||||
}
|
||||
|
||||
if (gecko) {
|
||||
e_stop(e);
|
||||
var mouseup = connect(window, "mouseup", function() {
|
||||
mouseup();
|
||||
setTimeout(rehide, 20);
|
||||
}, true);
|
||||
} else {
|
||||
setTimeout(rehide, 50);
|
||||
}
|
||||
}
|
||||
|
||||
// Cursor-blinking
|
||||
function restartBlink() {
|
||||
clearInterval(blinker);
|
||||
var on = true;
|
||||
cursor.style.visibility = "";
|
||||
blinker = setInterval(function() {
|
||||
cursor.style.visibility = (on = !on) ? "" : "hidden";
|
||||
}, 650);
|
||||
}
|
||||
|
||||
var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
|
||||
function matchBrackets(autoclear) {
|
||||
var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;
|
||||
var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
|
||||
if (!match) return;
|
||||
var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
|
||||
for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2)
|
||||
if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;}
|
||||
|
||||
var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
|
||||
function scan(line, from, to) {
|
||||
if (!line.text) return;
|
||||
var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur;
|
||||
for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) {
|
||||
var text = st[i];
|
||||
if (st[i+1] != null && st[i+1] != style) {pos += d * text.length; continue;}
|
||||
for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) {
|
||||
if (pos >= from && pos < to && re.test(cur = text.charAt(j))) {
|
||||
var match = matching[cur];
|
||||
if (match.charAt(1) == ">" == forward) stack.push(cur);
|
||||
else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
|
||||
else if (!stack.length) return {pos: pos, match: true};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {
|
||||
var line = getLine(i), first = i == head.line;
|
||||
var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
|
||||
if (found) break;
|
||||
}
|
||||
if (!found) found = {pos: null, match: false};
|
||||
var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
|
||||
var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
|
||||
two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
|
||||
var clear = operation(function(){one.clear(); two && two.clear();});
|
||||
if (autoclear) setTimeout(clear, 800);
|
||||
else bracketHighlighted = clear;
|
||||
}
|
||||
|
||||
// Finds the line to start with when starting a parse. Tries to
|
||||
// find a line with a stateAfter, so that it can start with a
|
||||
// valid state. If that fails, it returns the line with the
|
||||
// smallest indentation, which tends to need the least context to
|
||||
// parse correctly.
|
||||
function findStartLine(n) {
|
||||
var minindent, minline;
|
||||
for (var search = n, lim = n - 40; search > lim; --search) {
|
||||
if (search == 0) return 0;
|
||||
var line = getLine(search-1);
|
||||
if (line.stateAfter) return search;
|
||||
var indented = line.indentation(options.tabSize);
|
||||
if (minline == null || minindent > indented) {
|
||||
minline = search - 1;
|
||||
minindent = indented;
|
||||
}
|
||||
}
|
||||
return minline;
|
||||
}
|
||||
function getStateBefore(n) {
|
||||
var start = findStartLine(n), state = start && getLine(start-1).stateAfter;
|
||||
if (!state) state = startState(mode);
|
||||
else state = copyState(mode, state);
|
||||
doc.iter(start, n, function(line) {
|
||||
line.highlight(mode, state, options.tabSize);
|
||||
line.stateAfter = copyState(mode, state);
|
||||
});
|
||||
if (start < n) changes.push({from: start, to: n});
|
||||
if (n < doc.size && !getLine(n).stateAfter) work.push(n);
|
||||
return state;
|
||||
}
|
||||
function highlightLines(start, end) {
|
||||
var state = getStateBefore(start);
|
||||
doc.iter(start, end, function(line) {
|
||||
line.highlight(mode, state, options.tabSize);
|
||||
line.stateAfter = copyState(mode, state);
|
||||
});
|
||||
}
|
||||
function highlightWorker() {
|
||||
var end = +new Date + options.workTime;
|
||||
var foundWork = work.length;
|
||||
while (work.length) {
|
||||
if (!getLine(showingFrom).stateAfter) var task = showingFrom;
|
||||
else var task = work.pop();
|
||||
if (task >= doc.size) continue;
|
||||
var start = findStartLine(task), state = start && getLine(start-1).stateAfter;
|
||||
if (state) state = copyState(mode, state);
|
||||
else state = startState(mode);
|
||||
|
||||
var unchanged = 0, compare = mode.compareStates, realChange = false,
|
||||
i = start, bail = false;
|
||||
doc.iter(i, doc.size, function(line) {
|
||||
var hadState = line.stateAfter;
|
||||
if (+new Date > end) {
|
||||
work.push(i);
|
||||
startWorker(options.workDelay);
|
||||
if (realChange) changes.push({from: task, to: i + 1});
|
||||
return (bail = true);
|
||||
}
|
||||
var changed = line.highlight(mode, state, options.tabSize);
|
||||
if (changed) realChange = true;
|
||||
line.stateAfter = copyState(mode, state);
|
||||
if (compare) {
|
||||
if (hadState && compare(hadState, state)) return true;
|
||||
} else {
|
||||
if (changed !== false || !hadState) unchanged = 0;
|
||||
else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, "")))
|
||||
return true;
|
||||
}
|
||||
++i;
|
||||
});
|
||||
if (bail) return;
|
||||
if (realChange) changes.push({from: task, to: i + 1});
|
||||
}
|
||||
if (foundWork && options.onHighlightComplete)
|
||||
options.onHighlightComplete(instance);
|
||||
}
|
||||
function startWorker(time) {
|
||||
if (!work.length) return;
|
||||
highlight.set(time, operation(highlightWorker));
|
||||
}
|
||||
|
||||
// Operations are used to wrap changes in such a way that each
|
||||
// change won't have to update the cursor and display (which would
|
||||
// be awkward, slow, and error-prone), but instead updates are
|
||||
// batched and then all combined and executed at once.
|
||||
function startOperation() {
|
||||
updateInput = userSelChange = textChanged = null;
|
||||
changes = []; selectionChanged = false; callbacks = [];
|
||||
}
|
||||
function endOperation() {
|
||||
var reScroll = false, updated;
|
||||
if (selectionChanged) reScroll = !scrollCursorIntoView();
|
||||
if (changes.length) updated = updateDisplay(changes, true);
|
||||
else {
|
||||
if (selectionChanged) updateSelection();
|
||||
if (gutterDirty) updateGutter();
|
||||
}
|
||||
if (reScroll) scrollCursorIntoView();
|
||||
if (selectionChanged) {scrollEditorIntoView(); restartBlink();}
|
||||
|
||||
if (focused && !leaveInputAlone &&
|
||||
(updateInput === true || (updateInput !== false && selectionChanged)))
|
||||
resetInput(userSelChange);
|
||||
|
||||
if (selectionChanged && options.matchBrackets)
|
||||
setTimeout(operation(function() {
|
||||
if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
|
||||
if (posEq(sel.from, sel.to)) matchBrackets(false);
|
||||
}), 20);
|
||||
var tc = textChanged, cbs = callbacks; // these can be reset by callbacks
|
||||
if (selectionChanged && options.onCursorActivity)
|
||||
options.onCursorActivity(instance);
|
||||
if (tc && options.onChange && instance)
|
||||
options.onChange(instance, tc);
|
||||
for (var i = 0; i < cbs.length; ++i) cbs[i](instance);
|
||||
if (updated && options.onUpdate) options.onUpdate(instance);
|
||||
}
|
||||
var nestedOperation = 0;
|
||||
function operation(f) {
|
||||
return function() {
|
||||
if (!nestedOperation++) startOperation();
|
||||
try {var result = f.apply(this, arguments);}
|
||||
finally {if (!--nestedOperation) endOperation();}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
for (var ext in extensions)
|
||||
if (extensions.propertyIsEnumerable(ext) &&
|
||||
!instance.propertyIsEnumerable(ext))
|
||||
instance[ext] = extensions[ext];
|
||||
return instance;
|
||||
} // (end of function CodeMirror)
|
||||
|
||||
// The default configuration options.
|
||||
CodeMirror.defaults = {
|
||||
value: "",
|
||||
mode: null,
|
||||
theme: "default",
|
||||
indentUnit: 2,
|
||||
indentWithTabs: false,
|
||||
smartIndent: true,
|
||||
tabSize: 4,
|
||||
keyMap: "default",
|
||||
extraKeys: null,
|
||||
electricChars: true,
|
||||
autoClearEmptyLines: false,
|
||||
onKeyEvent: null,
|
||||
lineWrapping: false,
|
||||
lineNumbers: false,
|
||||
gutter: false,
|
||||
fixedGutter: false,
|
||||
firstLineNumber: 1,
|
||||
readOnly: false,
|
||||
onChange: null,
|
||||
onCursorActivity: null,
|
||||
onGutterClick: null,
|
||||
onHighlightComplete: null,
|
||||
onUpdate: null,
|
||||
onFocus: null, onBlur: null, onScroll: null,
|
||||
matchBrackets: false,
|
||||
workTime: 100,
|
||||
workDelay: 200,
|
||||
pollInterval: 100,
|
||||
undoDepth: 40,
|
||||
tabindex: null,
|
||||
autofocus: null
|
||||
};
|
||||
|
||||
var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
|
||||
var mac = ios || /Mac/.test(navigator.platform);
|
||||
var win = /Win/.test(navigator.platform);
|
||||
|
||||
// Known modes, by name and by MIME
|
||||
var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
|
||||
CodeMirror.defineMode = function(name, mode) {
|
||||
if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
|
||||
modes[name] = mode;
|
||||
};
|
||||
CodeMirror.defineMIME = function(mime, spec) {
|
||||
mimeModes[mime] = spec;
|
||||
};
|
||||
CodeMirror.resolveMode = function(spec) {
|
||||
if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
|
||||
spec = mimeModes[spec];
|
||||
else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec))
|
||||
return CodeMirror.resolveMode("application/xml");
|
||||
if (typeof spec == "string") return {name: spec};
|
||||
else return spec || {name: "null"};
|
||||
};
|
||||
CodeMirror.getMode = function(options, spec) {
|
||||
var spec = CodeMirror.resolveMode(spec);
|
||||
var mfactory = modes[spec.name];
|
||||
if (!mfactory) {
|
||||
if (window.console) console.warn("No mode " + spec.name + " found, falling back to plain text.");
|
||||
return CodeMirror.getMode(options, "text/plain");
|
||||
}
|
||||
return mfactory(options, spec);
|
||||
};
|
||||
CodeMirror.listModes = function() {
|
||||
var list = [];
|
||||
for (var m in modes)
|
||||
if (modes.propertyIsEnumerable(m)) list.push(m);
|
||||
return list;
|
||||
};
|
||||
CodeMirror.listMIMEs = function() {
|
||||
var list = [];
|
||||
for (var m in mimeModes)
|
||||
if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});
|
||||
return list;
|
||||
};
|
||||
|
||||
var extensions = CodeMirror.extensions = {};
|
||||
CodeMirror.defineExtension = function(name, func) {
|
||||
extensions[name] = func;
|
||||
};
|
||||
|
||||
var commands = CodeMirror.commands = {
|
||||
selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},
|
||||
killLine: function(cm) {
|
||||
var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
|
||||
if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0});
|
||||
else cm.replaceRange("", from, sel ? to : {line: from.line});
|
||||
},
|
||||
deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});},
|
||||
undo: function(cm) {cm.undo();},
|
||||
redo: function(cm) {cm.redo();},
|
||||
goDocStart: function(cm) {cm.setCursor(0, 0, true);},
|
||||
goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);},
|
||||
goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);},
|
||||
goLineStartSmart: function(cm) {
|
||||
var cur = cm.getCursor();
|
||||
var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/));
|
||||
cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);
|
||||
},
|
||||
goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);},
|
||||
goLineUp: function(cm) {cm.moveV(-1, "line");},
|
||||
goLineDown: function(cm) {cm.moveV(1, "line");},
|
||||
goPageUp: function(cm) {cm.moveV(-1, "page");},
|
||||
goPageDown: function(cm) {cm.moveV(1, "page");},
|
||||
goCharLeft: function(cm) {cm.moveH(-1, "char");},
|
||||
goCharRight: function(cm) {cm.moveH(1, "char");},
|
||||
goColumnLeft: function(cm) {cm.moveH(-1, "column");},
|
||||
goColumnRight: function(cm) {cm.moveH(1, "column");},
|
||||
goWordLeft: function(cm) {cm.moveH(-1, "word");},
|
||||
goWordRight: function(cm) {cm.moveH(1, "word");},
|
||||
delCharLeft: function(cm) {cm.deleteH(-1, "char");},
|
||||
delCharRight: function(cm) {cm.deleteH(1, "char");},
|
||||
delWordLeft: function(cm) {cm.deleteH(-1, "word");},
|
||||
delWordRight: function(cm) {cm.deleteH(1, "word");},
|
||||
indentAuto: function(cm) {cm.indentSelection("smart");},
|
||||
indentMore: function(cm) {cm.indentSelection("add");},
|
||||
indentLess: function(cm) {cm.indentSelection("subtract");},
|
||||
insertTab: function(cm) {cm.replaceSelection("\t", "end");},
|
||||
transposeChars: function(cm) {
|
||||
var cur = cm.getCursor(), line = cm.getLine(cur.line);
|
||||
if (cur.ch > 0 && cur.ch < line.length - 1)
|
||||
cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
|
||||
{line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});
|
||||
},
|
||||
newlineAndIndent: function(cm) {
|
||||
cm.replaceSelection("\n", "end");
|
||||
cm.indentLine(cm.getCursor().line);
|
||||
},
|
||||
toggleOverwrite: function(cm) {cm.toggleOverwrite();}
|
||||
};
|
||||
|
||||
var keyMap = CodeMirror.keyMap = {};
|
||||
keyMap.basic = {
|
||||
"Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
|
||||
"End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
|
||||
"Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "insertTab", "Shift-Tab": "indentAuto",
|
||||
"Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
|
||||
};
|
||||
// Note that the save and find-related commands aren't defined by
|
||||
// default. Unknown commands are simply ignored.
|
||||
keyMap.pcDefault = {
|
||||
"Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
|
||||
"Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
|
||||
"Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
|
||||
"Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find",
|
||||
"Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
|
||||
"Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
|
||||
fallthrough: "basic"
|
||||
};
|
||||
keyMap.macDefault = {
|
||||
"Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
|
||||
"Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
|
||||
"Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft",
|
||||
"Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find",
|
||||
"Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
|
||||
"Cmd-[": "indentLess", "Cmd-]": "indentMore",
|
||||
fallthrough: ["basic", "emacsy"]
|
||||
};
|
||||
keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
|
||||
keyMap.emacsy = {
|
||||
"Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
|
||||
"Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
|
||||
"Ctrl-V": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft",
|
||||
"Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
|
||||
};
|
||||
|
||||
function getKeyMap(val) {
|
||||
if (typeof val == "string") return keyMap[val];
|
||||
else return val;
|
||||
}
|
||||
function lookupKey(name, extraMap, map, handle) {
|
||||
function lookup(map) {
|
||||
map = getKeyMap(map);
|
||||
var found = map[name];
|
||||
if (found != null && handle(found)) return true;
|
||||
if (map.catchall) return handle(map.catchall);
|
||||
var fallthrough = map.fallthrough;
|
||||
if (fallthrough == null) return false;
|
||||
if (Object.prototype.toString.call(fallthrough) != "[object Array]")
|
||||
return lookup(fallthrough);
|
||||
for (var i = 0, e = fallthrough.length; i < e; ++i) {
|
||||
if (lookup(fallthrough[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (extraMap && lookup(extraMap)) return true;
|
||||
return lookup(map);
|
||||
}
|
||||
function isModifierKey(event) {
|
||||
var name = keyNames[e_prop(event, "keyCode")];
|
||||
return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
|
||||
}
|
||||
|
||||
CodeMirror.fromTextArea = function(textarea, options) {
|
||||
if (!options) options = {};
|
||||
options.value = textarea.value;
|
||||
if (!options.tabindex && textarea.tabindex)
|
||||
options.tabindex = textarea.tabindex;
|
||||
if (options.autofocus == null && textarea.getAttribute("autofocus") != null)
|
||||
options.autofocus = true;
|
||||
|
||||
function save() {textarea.value = instance.getValue();}
|
||||
if (textarea.form) {
|
||||
// Deplorable hack to make the submit method do the right thing.
|
||||
var rmSubmit = connect(textarea.form, "submit", save, true);
|
||||
if (typeof textarea.form.submit == "function") {
|
||||
var realSubmit = textarea.form.submit;
|
||||
function wrappedSubmit() {
|
||||
save();
|
||||
textarea.form.submit = realSubmit;
|
||||
textarea.form.submit();
|
||||
textarea.form.submit = wrappedSubmit;
|
||||
}
|
||||
textarea.form.submit = wrappedSubmit;
|
||||
}
|
||||
}
|
||||
|
||||
textarea.style.display = "none";
|
||||
var instance = CodeMirror(function(node) {
|
||||
textarea.parentNode.insertBefore(node, textarea.nextSibling);
|
||||
}, options);
|
||||
instance.save = save;
|
||||
instance.getTextArea = function() { return textarea; };
|
||||
instance.toTextArea = function() {
|
||||
save();
|
||||
textarea.parentNode.removeChild(instance.getWrapperElement());
|
||||
textarea.style.display = "";
|
||||
if (textarea.form) {
|
||||
rmSubmit();
|
||||
if (typeof textarea.form.submit == "function")
|
||||
textarea.form.submit = realSubmit;
|
||||
}
|
||||
};
|
||||
return instance;
|
||||
};
|
||||
|
||||
// Utility functions for working with state. Exported because modes
|
||||
// sometimes need to do this.
|
||||
function copyState(mode, state) {
|
||||
if (state === true) return state;
|
||||
if (mode.copyState) return mode.copyState(state);
|
||||
var nstate = {};
|
||||
for (var n in state) {
|
||||
var val = state[n];
|
||||
if (val instanceof Array) val = val.concat([]);
|
||||
nstate[n] = val;
|
||||
}
|
||||
return nstate;
|
||||
}
|
||||
CodeMirror.copyState = copyState;
|
||||
function startState(mode, a1, a2) {
|
||||
return mode.startState ? mode.startState(a1, a2) : true;
|
||||
}
|
||||
CodeMirror.startState = startState;
|
||||
|
||||
// The character stream used by a mode's parser.
|
||||
function StringStream(string, tabSize) {
|
||||
this.pos = this.start = 0;
|
||||
this.string = string;
|
||||
this.tabSize = tabSize || 8;
|
||||
}
|
||||
StringStream.prototype = {
|
||||
eol: function() {return this.pos >= this.string.length;},
|
||||
sol: function() {return this.pos == 0;},
|
||||
peek: function() {return this.string.charAt(this.pos);},
|
||||
next: function() {
|
||||
if (this.pos < this.string.length)
|
||||
return this.string.charAt(this.pos++);
|
||||
},
|
||||
eat: function(match) {
|
||||
var ch = this.string.charAt(this.pos);
|
||||
if (typeof match == "string") var ok = ch == match;
|
||||
else var ok = ch && (match.test ? match.test(ch) : match(ch));
|
||||
if (ok) {++this.pos; return ch;}
|
||||
},
|
||||
eatWhile: function(match) {
|
||||
var start = this.pos;
|
||||
while (this.eat(match)){}
|
||||
return this.pos > start;
|
||||
},
|
||||
eatSpace: function() {
|
||||
var start = this.pos;
|
||||
while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
|
||||
return this.pos > start;
|
||||
},
|
||||
skipToEnd: function() {this.pos = this.string.length;},
|
||||
skipTo: function(ch) {
|
||||
var found = this.string.indexOf(ch, this.pos);
|
||||
if (found > -1) {this.pos = found; return true;}
|
||||
},
|
||||
backUp: function(n) {this.pos -= n;},
|
||||
column: function() {return countColumn(this.string, this.start, this.tabSize);},
|
||||
indentation: function() {return countColumn(this.string, null, this.tabSize);},
|
||||
match: function(pattern, consume, caseInsensitive) {
|
||||
if (typeof pattern == "string") {
|
||||
function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
|
||||
if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
|
||||
if (consume !== false) this.pos += pattern.length;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
var match = this.string.slice(this.pos).match(pattern);
|
||||
if (match && consume !== false) this.pos += match[0].length;
|
||||
return match;
|
||||
}
|
||||
},
|
||||
current: function(){return this.string.slice(this.start, this.pos);}
|
||||
};
|
||||
CodeMirror.StringStream = StringStream;
|
||||
|
||||
function MarkedText(from, to, className, marker) {
|
||||
this.from = from; this.to = to; this.style = className; this.marker = marker;
|
||||
}
|
||||
MarkedText.prototype = {
|
||||
attach: function(line) { this.marker.set.push(line); },
|
||||
detach: function(line) {
|
||||
var ix = indexOf(this.marker.set, line);
|
||||
if (ix > -1) this.marker.set.splice(ix, 1);
|
||||
},
|
||||
split: function(pos, lenBefore) {
|
||||
if (this.to <= pos && this.to != null) return null;
|
||||
var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;
|
||||
var to = this.to == null ? null : this.to - pos + lenBefore;
|
||||
return new MarkedText(from, to, this.style, this.marker);
|
||||
},
|
||||
dup: function() { return new MarkedText(null, null, this.style, this.marker); },
|
||||
clipTo: function(fromOpen, from, toOpen, to, diff) {
|
||||
if (fromOpen && to > this.from && (to < this.to || this.to == null))
|
||||
this.from = null;
|
||||
else if (this.from != null && this.from >= from)
|
||||
this.from = Math.max(to, this.from) + diff;
|
||||
if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
|
||||
this.to = null;
|
||||
else if (this.to != null && this.to > from)
|
||||
this.to = to < this.to ? this.to + diff : from;
|
||||
},
|
||||
isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },
|
||||
sameSet: function(x) { return this.marker == x.marker; }
|
||||
};
|
||||
|
||||
function Bookmark(pos) {
|
||||
this.from = pos; this.to = pos; this.line = null;
|
||||
}
|
||||
Bookmark.prototype = {
|
||||
attach: function(line) { this.line = line; },
|
||||
detach: function(line) { if (this.line == line) this.line = null; },
|
||||
split: function(pos, lenBefore) {
|
||||
if (pos < this.from) {
|
||||
this.from = this.to = (this.from - pos) + lenBefore;
|
||||
return this;
|
||||
}
|
||||
},
|
||||
isDead: function() { return this.from > this.to; },
|
||||
clipTo: function(fromOpen, from, toOpen, to, diff) {
|
||||
if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {
|
||||
this.from = 0; this.to = -1;
|
||||
} else if (this.from > from) {
|
||||
this.from = this.to = Math.max(to, this.from) + diff;
|
||||
}
|
||||
},
|
||||
sameSet: function(x) { return false; },
|
||||
find: function() {
|
||||
if (!this.line || !this.line.parent) return null;
|
||||
return {line: lineNo(this.line), ch: this.from};
|
||||
},
|
||||
clear: function() {
|
||||
if (this.line) {
|
||||
var found = indexOf(this.line.marked, this);
|
||||
if (found != -1) this.line.marked.splice(found, 1);
|
||||
this.line = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Line objects. These hold state related to a line, including
|
||||
// highlighting info (the styles array).
|
||||
function Line(text, styles) {
|
||||
this.styles = styles || [text, null];
|
||||
this.text = text;
|
||||
this.height = 1;
|
||||
this.marked = this.gutterMarker = this.className = this.bgClassName = this.handlers = null;
|
||||
this.stateAfter = this.parent = this.hidden = null;
|
||||
this.widgetFunction = null;
|
||||
}
|
||||
Line.inheritMarks = function(text, orig) {
|
||||
var ln = new Line(text), mk = orig && orig.marked;
|
||||
if (mk) {
|
||||
for (var i = 0; i < mk.length; ++i) {
|
||||
if (mk[i].to == null && mk[i].style) {
|
||||
var newmk = ln.marked || (ln.marked = []), mark = mk[i];
|
||||
var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ln;
|
||||
}
|
||||
Line.prototype = {
|
||||
// Replace a piece of a line, keeping the styles around it intact.
|
||||
replace: function(from, to_, text) {
|
||||
var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
|
||||
copyStyles(0, from, this.styles, st);
|
||||
if (text) st.push(text, null);
|
||||
copyStyles(to, this.text.length, this.styles, st);
|
||||
this.styles = st;
|
||||
this.text = this.text.slice(0, from) + text + this.text.slice(to);
|
||||
this.stateAfter = null;
|
||||
if (mk) {
|
||||
var diff = text.length - (to - from);
|
||||
for (var i = 0; i < mk.length; ++i) {
|
||||
var mark = mk[i];
|
||||
mark.clipTo(from == null, from || 0, to_ == null, to, diff);
|
||||
if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Split a part off a line, keeping styles and markers intact.
|
||||
split: function(pos, textBefore) {
|
||||
var st = [textBefore, null], mk = this.marked;
|
||||
copyStyles(pos, this.text.length, this.styles, st);
|
||||
var taken = new Line(textBefore + this.text.slice(pos), st);
|
||||
if (mk) {
|
||||
for (var i = 0; i < mk.length; ++i) {
|
||||
var mark = mk[i];
|
||||
var newmark = mark.split(pos, textBefore.length);
|
||||
if (newmark) {
|
||||
if (!taken.marked) taken.marked = [];
|
||||
taken.marked.push(newmark); newmark.attach(taken);
|
||||
if (newmark == mark) mk.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return taken;
|
||||
},
|
||||
append: function(line) {
|
||||
var mylen = this.text.length, mk = line.marked, mymk = this.marked;
|
||||
this.text += line.text;
|
||||
copyStyles(0, line.text.length, line.styles, this.styles);
|
||||
if (mymk) {
|
||||
for (var i = 0; i < mymk.length; ++i)
|
||||
if (mymk[i].to == null) mymk[i].to = mylen;
|
||||
}
|
||||
if (mk && mk.length) {
|
||||
if (!mymk) this.marked = mymk = [];
|
||||
outer: for (var i = 0; i < mk.length; ++i) {
|
||||
var mark = mk[i];
|
||||
if (!mark.from) {
|
||||
for (var j = 0; j < mymk.length; ++j) {
|
||||
var mymark = mymk[j];
|
||||
if (mymark.to == mylen && mymark.sameSet(mark)) {
|
||||
mymark.to = mark.to == null ? null : mark.to + mylen;
|
||||
if (mymark.isDead()) {
|
||||
mymark.detach(this);
|
||||
mk.splice(i--, 1);
|
||||
}
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
mymk.push(mark);
|
||||
mark.attach(this);
|
||||
mark.from += mylen;
|
||||
if (mark.to != null) mark.to += mylen;
|
||||
}
|
||||
}
|
||||
},
|
||||
fixMarkEnds: function(other) {
|
||||
var mk = this.marked, omk = other.marked;
|
||||
if (!mk) return;
|
||||
for (var i = 0; i < mk.length; ++i) {
|
||||
var mark = mk[i], close = mark.to == null;
|
||||
if (close && omk) {
|
||||
for (var j = 0; j < omk.length; ++j)
|
||||
if (omk[j].sameSet(mark)) {close = false; break;}
|
||||
}
|
||||
if (close) mark.to = this.text.length;
|
||||
}
|
||||
},
|
||||
fixMarkStarts: function() {
|
||||
var mk = this.marked;
|
||||
if (!mk) return;
|
||||
for (var i = 0; i < mk.length; ++i)
|
||||
if (mk[i].from == null) mk[i].from = 0;
|
||||
},
|
||||
addMark: function(mark) {
|
||||
mark.attach(this);
|
||||
if (this.marked == null) this.marked = [];
|
||||
this.marked.push(mark);
|
||||
this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
|
||||
},
|
||||
// Run the given mode's parser over a line, update the styles
|
||||
// array, which contains alternating fragments of text and CSS
|
||||
// classes.
|
||||
highlight: function(mode, state, tabSize) {
|
||||
var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0;
|
||||
var changed = false, curWord = st[0], prevWord;
|
||||
if (this.text == "" && mode.blankLine) mode.blankLine(state);
|
||||
while (!stream.eol()) {
|
||||
var style = mode.token(stream, state);
|
||||
var substr = this.text.slice(stream.start, stream.pos);
|
||||
stream.start = stream.pos;
|
||||
if (pos && st[pos-1] == style)
|
||||
st[pos-2] += substr;
|
||||
else if (substr) {
|
||||
if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true;
|
||||
st[pos++] = substr; st[pos++] = style;
|
||||
prevWord = curWord; curWord = st[pos];
|
||||
}
|
||||
// Give up when line is ridiculously long
|
||||
if (stream.pos > 5000) {
|
||||
st[pos++] = this.text.slice(stream.pos); st[pos++] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (st.length != pos) {st.length = pos; changed = true;}
|
||||
if (pos && st[pos-2] != prevWord) changed = true;
|
||||
if (st.length == 2 && typeof st[1] == 'object') {
|
||||
this.widgetFunction = st[1];
|
||||
st[1] = null;
|
||||
} else {
|
||||
this.widgetFunction = null;
|
||||
}
|
||||
// Short lines with simple highlights return null, and are
|
||||
// counted as changed by the driver because they are likely to
|
||||
// highlight the same way in various contexts.
|
||||
return changed || (st.length < 5 && this.text.length < 10 ? null : false);
|
||||
},
|
||||
nodeAdded: function(node) {
|
||||
if (this.widgetFunction) this.widgetFunction.callback(node, this);
|
||||
},
|
||||
// Fetch the parser token for a given character. Useful for hacks
|
||||
// that want to inspect the mode state (say, for completion).
|
||||
getTokenAt: function(mode, state, ch) {
|
||||
var txt = this.text, stream = new StringStream(txt);
|
||||
while (stream.pos < ch && !stream.eol()) {
|
||||
stream.start = stream.pos;
|
||||
var style = mode.token(stream, state);
|
||||
}
|
||||
return {start: stream.start,
|
||||
end: stream.pos,
|
||||
string: stream.current(),
|
||||
className: style || null,
|
||||
state: state};
|
||||
},
|
||||
indentation: function(tabSize) {return countColumn(this.text, null, tabSize);},
|
||||
// Produces an HTML fragment for the line, taking selection,
|
||||
// marking, and highlighting into account.
|
||||
getHTML: function(makeTab, endAt) {
|
||||
var html = [], first = true, col = 0;
|
||||
function span(text, style) {
|
||||
if (!text) return;
|
||||
// Work around a bug where, in some compat modes, IE ignores leading spaces
|
||||
if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
|
||||
first = false;
|
||||
if (text.indexOf("\t") == -1) {
|
||||
col += text.length;
|
||||
var escaped = htmlEscape(text);
|
||||
} else {
|
||||
var escaped = "";
|
||||
for (var pos = 0;;) {
|
||||
var idx = text.indexOf("\t", pos);
|
||||
if (idx == -1) {
|
||||
escaped += htmlEscape(text.slice(pos));
|
||||
col += text.length - pos;
|
||||
break;
|
||||
} else {
|
||||
col += idx - pos;
|
||||
var tab = makeTab(col);
|
||||
escaped += htmlEscape(text.slice(pos, idx)) + tab.html;
|
||||
col += tab.width;
|
||||
pos = idx + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (style) html.push('<span class="', style, '">', escaped, "</span>");
|
||||
else html.push(escaped);
|
||||
}
|
||||
var st = this.styles, allText = this.text, marked = this.marked;
|
||||
var len = allText.length;
|
||||
if (this.widgetFunction) return this.widgetFunction.creator(allText);
|
||||
if (endAt != null) len = Math.min(endAt, len);
|
||||
function styleToClass(style) {
|
||||
if (!style) return null;
|
||||
return "cm-" + style.replace(/ +/g, " cm-");
|
||||
}
|
||||
|
||||
if (!allText && endAt == null)
|
||||
span(" ");
|
||||
else if (!marked || !marked.length)
|
||||
for (var i = 0, ch = 0; ch < len; i+=2) {
|
||||
var str = st[i], style = st[i+1], l = str.length;
|
||||
if (ch + l > len) str = str.slice(0, len - ch);
|
||||
ch += l;
|
||||
span(str, styleToClass(style));
|
||||
}
|
||||
else {
|
||||
var pos = 0, i = 0, text = "", style, sg = 0;
|
||||
var nextChange = marked[0].from || 0, marks = [], markpos = 0;
|
||||
function advanceMarks() {
|
||||
var m;
|
||||
while (markpos < marked.length &&
|
||||
((m = marked[markpos]).from == pos || m.from == null)) {
|
||||
if (m.style != null) marks.push(m);
|
||||
++markpos;
|
||||
}
|
||||
nextChange = markpos < marked.length ? marked[markpos].from : Infinity;
|
||||
for (var i = 0; i < marks.length; ++i) {
|
||||
var to = marks[i].to || Infinity;
|
||||
if (to == pos) marks.splice(i--, 1);
|
||||
else nextChange = Math.min(to, nextChange);
|
||||
}
|
||||
}
|
||||
var m = 0;
|
||||
while (pos < len) {
|
||||
if (nextChange == pos) advanceMarks();
|
||||
var upto = Math.min(len, nextChange);
|
||||
while (true) {
|
||||
if (text) {
|
||||
var end = pos + text.length;
|
||||
var appliedStyle = style;
|
||||
for (var j = 0; j < marks.length; ++j)
|
||||
appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style;
|
||||
span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
|
||||
if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
|
||||
pos = end;
|
||||
}
|
||||
text = st[i++]; style = styleToClass(st[i++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return html.join("");
|
||||
},
|
||||
cleanUp: function() {
|
||||
this.parent = null;
|
||||
if (this.marked)
|
||||
for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);
|
||||
}
|
||||
};
|
||||
// Utility used by replace and split above
|
||||
function copyStyles(from, to, source, dest) {
|
||||
for (var i = 0, pos = 0, state = 0; pos < to; i+=2) {
|
||||
var part = source[i], end = pos + part.length;
|
||||
if (state == 0) {
|
||||
if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]);
|
||||
if (end >= from) state = 1;
|
||||
}
|
||||
else if (state == 1) {
|
||||
if (end > to) dest.push(part.slice(0, to - pos), source[i+1]);
|
||||
else dest.push(part, source[i+1]);
|
||||
}
|
||||
pos = end;
|
||||
}
|
||||
}
|
||||
|
||||
// Data structure that holds the sequence of lines.
|
||||
function LeafChunk(lines) {
|
||||
this.lines = lines;
|
||||
this.parent = null;
|
||||
for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
|
||||
lines[i].parent = this;
|
||||
height += lines[i].height;
|
||||
}
|
||||
this.height = height;
|
||||
}
|
||||
LeafChunk.prototype = {
|
||||
chunkSize: function() { return this.lines.length; },
|
||||
remove: function(at, n, callbacks) {
|
||||
for (var i = at, e = at + n; i < e; ++i) {
|
||||
var line = this.lines[i];
|
||||
this.height -= line.height;
|
||||
line.cleanUp();
|
||||
if (line.handlers)
|
||||
for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]);
|
||||
}
|
||||
this.lines.splice(at, n);
|
||||
},
|
||||
collapse: function(lines) {
|
||||
lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
|
||||
},
|
||||
insertHeight: function(at, lines, height) {
|
||||
this.height += height;
|
||||
this.lines.splice.apply(this.lines, [at, 0].concat(lines));
|
||||
for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
|
||||
},
|
||||
iterN: function(at, n, op) {
|
||||
for (var e = at + n; at < e; ++at)
|
||||
if (op(this.lines[at])) return true;
|
||||
}
|
||||
};
|
||||
function BranchChunk(children) {
|
||||
this.children = children;
|
||||
var size = 0, height = 0;
|
||||
for (var i = 0, e = children.length; i < e; ++i) {
|
||||
var ch = children[i];
|
||||
size += ch.chunkSize(); height += ch.height;
|
||||
ch.parent = this;
|
||||
}
|
||||
this.size = size;
|
||||
this.height = height;
|
||||
this.parent = null;
|
||||
}
|
||||
BranchChunk.prototype = {
|
||||
chunkSize: function() { return this.size; },
|
||||
remove: function(at, n, callbacks) {
|
||||
this.size -= n;
|
||||
for (var i = 0; i < this.children.length; ++i) {
|
||||
var child = this.children[i], sz = child.chunkSize();
|
||||
if (at < sz) {
|
||||
var rm = Math.min(n, sz - at), oldHeight = child.height;
|
||||
child.remove(at, rm, callbacks);
|
||||
this.height -= oldHeight - child.height;
|
||||
if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
|
||||
if ((n -= rm) == 0) break;
|
||||
at = 0;
|
||||
} else at -= sz;
|
||||
}
|
||||
if (this.size - n < 25) {
|
||||
var lines = [];
|
||||
this.collapse(lines);
|
||||
this.children = [new LeafChunk(lines)];
|
||||
this.children[0].parent = this;
|
||||
}
|
||||
},
|
||||
collapse: function(lines) {
|
||||
for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
|
||||
},
|
||||
insert: function(at, lines) {
|
||||
var height = 0;
|
||||
for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
|
||||
this.insertHeight(at, lines, height);
|
||||
},
|
||||
insertHeight: function(at, lines, height) {
|
||||
this.size += lines.length;
|
||||
this.height += height;
|
||||
for (var i = 0, e = this.children.length; i < e; ++i) {
|
||||
var child = this.children[i], sz = child.chunkSize();
|
||||
if (at <= sz) {
|
||||
child.insertHeight(at, lines, height);
|
||||
if (child.lines && child.lines.length > 50) {
|
||||
while (child.lines.length > 50) {
|
||||
var spilled = child.lines.splice(child.lines.length - 25, 25);
|
||||
var newleaf = new LeafChunk(spilled);
|
||||
child.height -= newleaf.height;
|
||||
this.children.splice(i + 1, 0, newleaf);
|
||||
newleaf.parent = this;
|
||||
}
|
||||
this.maybeSpill();
|
||||
}
|
||||
break;
|
||||
}
|
||||
at -= sz;
|
||||
}
|
||||
},
|
||||
maybeSpill: function() {
|
||||
if (this.children.length <= 10) return;
|
||||
var me = this;
|
||||
do {
|
||||
var spilled = me.children.splice(me.children.length - 5, 5);
|
||||
var sibling = new BranchChunk(spilled);
|
||||
if (!me.parent) { // Become the parent node
|
||||
var copy = new BranchChunk(me.children);
|
||||
copy.parent = me;
|
||||
me.children = [copy, sibling];
|
||||
me = copy;
|
||||
} else {
|
||||
me.size -= sibling.size;
|
||||
me.height -= sibling.height;
|
||||
var myIndex = indexOf(me.parent.children, me);
|
||||
me.parent.children.splice(myIndex + 1, 0, sibling);
|
||||
}
|
||||
sibling.parent = me.parent;
|
||||
} while (me.children.length > 10);
|
||||
me.parent.maybeSpill();
|
||||
},
|
||||
iter: function(from, to, op) { this.iterN(from, to - from, op); },
|
||||
iterN: function(at, n, op) {
|
||||
for (var i = 0, e = this.children.length; i < e; ++i) {
|
||||
var child = this.children[i], sz = child.chunkSize();
|
||||
if (at < sz) {
|
||||
var used = Math.min(n, sz - at);
|
||||
if (child.iterN(at, used, op)) return true;
|
||||
if ((n -= used) == 0) break;
|
||||
at = 0;
|
||||
} else at -= sz;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getLineAt(chunk, n) {
|
||||
while (!chunk.lines) {
|
||||
for (var i = 0;; ++i) {
|
||||
var child = chunk.children[i], sz = child.chunkSize();
|
||||
if (n < sz) { chunk = child; break; }
|
||||
n -= sz;
|
||||
}
|
||||
}
|
||||
return chunk.lines[n];
|
||||
}
|
||||
function lineNo(line) {
|
||||
if (line.parent == null) return null;
|
||||
var cur = line.parent, no = indexOf(cur.lines, line);
|
||||
for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
|
||||
for (var i = 0, e = chunk.children.length; ; ++i) {
|
||||
if (chunk.children[i] == cur) break;
|
||||
no += chunk.children[i].chunkSize();
|
||||
}
|
||||
}
|
||||
return no;
|
||||
}
|
||||
function lineAtHeight(chunk, h) {
|
||||
var n = 0;
|
||||
outer: do {
|
||||
for (var i = 0, e = chunk.children.length; i < e; ++i) {
|
||||
var child = chunk.children[i], ch = child.height;
|
||||
if (h < ch) { chunk = child; continue outer; }
|
||||
h -= ch;
|
||||
n += child.chunkSize();
|
||||
}
|
||||
return n;
|
||||
} while (!chunk.lines);
|
||||
for (var i = 0, e = chunk.lines.length; i < e; ++i) {
|
||||
var line = chunk.lines[i], lh = line.height;
|
||||
if (h < lh) break;
|
||||
h -= lh;
|
||||
}
|
||||
return n + i;
|
||||
}
|
||||
function heightAtLine(chunk, n) {
|
||||
var h = 0;
|
||||
outer: do {
|
||||
for (var i = 0, e = chunk.children.length; i < e; ++i) {
|
||||
var child = chunk.children[i], sz = child.chunkSize();
|
||||
if (n < sz) { chunk = child; continue outer; }
|
||||
n -= sz;
|
||||
h += child.height;
|
||||
}
|
||||
return h;
|
||||
} while (!chunk.lines);
|
||||
for (var i = 0; i < n; ++i) h += chunk.lines[i].height;
|
||||
return h;
|
||||
}
|
||||
|
||||
// The history object 'chunks' changes that are made close together
|
||||
// and at almost the same time into bigger undoable units.
|
||||
function History() {
|
||||
this.time = 0;
|
||||
this.done = []; this.undone = [];
|
||||
}
|
||||
History.prototype = {
|
||||
addChange: function(start, added, old) {
|
||||
this.undone.length = 0;
|
||||
var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1];
|
||||
var dtime = time - this.time;
|
||||
if (dtime > 400 || !last) {
|
||||
this.done.push([{start: start, added: added, old: old}]);
|
||||
} else if (last.start > start + old.length || last.start + last.added < start - last.added + last.old.length) {
|
||||
cur.push({start: start, added: added, old: old});
|
||||
} else {
|
||||
var oldoff = 0;
|
||||
if (start < last.start) {
|
||||
for (var i = last.start - start - 1; i >= 0; --i)
|
||||
last.old.unshift(old[i]);
|
||||
oldoff = Math.min(0, added - old.length);
|
||||
last.added += last.start - start + oldoff;
|
||||
last.start = start;
|
||||
} else if (last.start < start) {
|
||||
oldoff = start - last.start;
|
||||
added += oldoff;
|
||||
}
|
||||
for (var i = last.added - oldoff, e = old.length; i < e; ++i)
|
||||
last.old.push(old[i]);
|
||||
if (last.added < added) last.added = added;
|
||||
}
|
||||
this.time = time;
|
||||
}
|
||||
};
|
||||
|
||||
function stopMethod() {e_stop(this);}
|
||||
// Ensure an event has a stop method.
|
||||
function addStop(event) {
|
||||
if (!event.stop) event.stop = stopMethod;
|
||||
return event;
|
||||
}
|
||||
|
||||
function e_preventDefault(e) {
|
||||
if (e.preventDefault) e.preventDefault();
|
||||
else e.returnValue = false;
|
||||
}
|
||||
function e_stopPropagation(e) {
|
||||
if (e.stopPropagation) e.stopPropagation();
|
||||
else e.cancelBubble = true;
|
||||
}
|
||||
function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
|
||||
CodeMirror.e_stop = e_stop;
|
||||
CodeMirror.e_preventDefault = e_preventDefault;
|
||||
CodeMirror.e_stopPropagation = e_stopPropagation;
|
||||
|
||||
function e_target(e) {return e.target || e.srcElement;}
|
||||
function e_button(e) {
|
||||
if (e.which) return e.which;
|
||||
else if (e.button & 1) return 1;
|
||||
else if (e.button & 2) return 3;
|
||||
else if (e.button & 4) return 2;
|
||||
}
|
||||
|
||||
// Allow 3rd-party code to override event properties by adding an override
|
||||
// object to an event object.
|
||||
function e_prop(e, prop) {
|
||||
var overridden = e.override && e.override.hasOwnProperty(prop);
|
||||
return overridden ? e.override[prop] : e[prop];
|
||||
}
|
||||
|
||||
// Event handler registration. If disconnect is true, it'll return a
|
||||
// function that unregisters the handler.
|
||||
function connect(node, type, handler, disconnect) {
|
||||
if (typeof node.addEventListener == "function") {
|
||||
node.addEventListener(type, handler, false);
|
||||
if (disconnect) return function() {node.removeEventListener(type, handler, false);};
|
||||
}
|
||||
else {
|
||||
var wrapHandler = function(event) {handler(event || window.event);};
|
||||
node.attachEvent("on" + type, wrapHandler);
|
||||
if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);};
|
||||
}
|
||||
}
|
||||
CodeMirror.connect = connect;
|
||||
|
||||
function Delayed() {this.id = null;}
|
||||
Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
|
||||
|
||||
var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
|
||||
|
||||
var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
|
||||
var ie = /MSIE \d/.test(navigator.userAgent);
|
||||
var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
|
||||
var webkit = /WebKit\//.test(navigator.userAgent);
|
||||
var chrome = /Chrome\//.test(navigator.userAgent);
|
||||
var khtml = /KHTML\//.test(navigator.userAgent);
|
||||
|
||||
// Detect drag-and-drop
|
||||
var dragAndDrop = function() {
|
||||
// There is *some* kind of drag-and-drop support in IE6-8, but I
|
||||
// couldn't get it to work yet.
|
||||
if (ie_lt9) return false;
|
||||
var div = document.createElement('div');
|
||||
return "draggable" in div || "dragDrop" in div;
|
||||
}();
|
||||
|
||||
var lineSep = "\n";
|
||||
// Feature-detect whether newlines in textareas are converted to \r\n
|
||||
(function () {
|
||||
var te = document.createElement("textarea");
|
||||
te.value = "foo\nbar";
|
||||
if (te.value.indexOf("\r") > -1) lineSep = "\r\n";
|
||||
}());
|
||||
|
||||
// Counts the column offset in a string, taking tabs into account.
|
||||
// Used mostly to find indentation.
|
||||
function countColumn(string, end, tabSize) {
|
||||
if (end == null) {
|
||||
end = string.search(/[^\s\u00a0]/);
|
||||
if (end == -1) end = string.length;
|
||||
}
|
||||
for (var i = 0, n = 0; i < end; ++i) {
|
||||
if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
|
||||
else ++n;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function computedStyle(elt) {
|
||||
if (elt.currentStyle) return elt.currentStyle;
|
||||
return window.getComputedStyle(elt, null);
|
||||
}
|
||||
|
||||
// Find the position of an element by following the offsetParent chain.
|
||||
// If screen==true, it returns screen (rather than page) coordinates.
|
||||
function eltOffset(node, screen) {
|
||||
var bod = node.ownerDocument.body;
|
||||
var x = 0, y = 0, skipBody = false;
|
||||
for (var n = node; n; n = n.offsetParent) {
|
||||
var ol = n.offsetLeft, ot = n.offsetTop;
|
||||
// Firefox reports weird inverted offsets when the body has a border.
|
||||
if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); }
|
||||
else { x += ol, y += ot; }
|
||||
if (screen && computedStyle(n).position == "fixed")
|
||||
skipBody = true;
|
||||
}
|
||||
var e = screen && !skipBody ? null : bod;
|
||||
for (var n = node.parentNode; n != e; n = n.parentNode)
|
||||
if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;}
|
||||
return {left: x, top: y};
|
||||
}
|
||||
// Use the faster and saner getBoundingClientRect method when possible.
|
||||
if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) {
|
||||
// Take the parts of bounding client rect that we are interested in so we are able to edit if need be,
|
||||
// since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)
|
||||
try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; }
|
||||
catch(e) { box = {top: 0, left: 0}; }
|
||||
if (!screen) {
|
||||
// Get the toplevel scroll, working around browser differences.
|
||||
if (window.pageYOffset == null) {
|
||||
var t = document.documentElement || document.body.parentNode;
|
||||
if (t.scrollTop == null) t = document.body;
|
||||
box.top += t.scrollTop; box.left += t.scrollLeft;
|
||||
} else {
|
||||
box.top += window.pageYOffset; box.left += window.pageXOffset;
|
||||
}
|
||||
}
|
||||
return box;
|
||||
};
|
||||
|
||||
// Get a node's text content.
|
||||
function eltText(node) {
|
||||
return node.textContent || node.innerText || node.nodeValue || "";
|
||||
}
|
||||
function selectInput(node) {
|
||||
if (ios) { // Mobile Safari apparently has a bug where select() is broken.
|
||||
node.selectionStart = 0;
|
||||
node.selectionEnd = node.value.length;
|
||||
} else node.select();
|
||||
}
|
||||
|
||||
// Operations on {line, ch} objects.
|
||||
function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
|
||||
function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
|
||||
function copyPos(x) {return {line: x.line, ch: x.ch};}
|
||||
|
||||
var escapeElement = document.createElement("pre");
|
||||
function htmlEscape(str) {
|
||||
escapeElement.textContent = str;
|
||||
return escapeElement.innerHTML;
|
||||
}
|
||||
// Recent (late 2011) Opera betas insert bogus newlines at the start
|
||||
// of the textContent, so we strip those.
|
||||
if (htmlEscape("a") == "\na")
|
||||
htmlEscape = function(str) {
|
||||
escapeElement.textContent = str;
|
||||
return escapeElement.innerHTML.slice(1);
|
||||
};
|
||||
// Some IEs don't preserve tabs through innerHTML
|
||||
else if (htmlEscape("\t") != "\t")
|
||||
htmlEscape = function(str) {
|
||||
escapeElement.innerHTML = "";
|
||||
escapeElement.appendChild(document.createTextNode(str));
|
||||
return escapeElement.innerHTML;
|
||||
};
|
||||
CodeMirror.htmlEscape = htmlEscape;
|
||||
|
||||
// Used to position the cursor after an undo/redo by finding the
|
||||
// last edited character.
|
||||
function editEnd(from, to) {
|
||||
if (!to) return 0;
|
||||
if (!from) return to.length;
|
||||
for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j)
|
||||
if (from.charAt(i) != to.charAt(j)) break;
|
||||
return j + 1;
|
||||
}
|
||||
|
||||
function indexOf(collection, elt) {
|
||||
if (collection.indexOf) return collection.indexOf(elt);
|
||||
for (var i = 0, e = collection.length; i < e; ++i)
|
||||
if (collection[i] == elt) return i;
|
||||
return -1;
|
||||
}
|
||||
function isWordChar(ch) {
|
||||
return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase();
|
||||
}
|
||||
|
||||
// See if "".split is the broken IE version, if so, provide an
|
||||
// alternative way to split lines.
|
||||
var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
|
||||
var pos = 0, nl, result = [];
|
||||
while ((nl = string.indexOf("\n", pos)) > -1) {
|
||||
result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl));
|
||||
pos = nl + 1;
|
||||
}
|
||||
result.push(string.slice(pos));
|
||||
return result;
|
||||
} : function(string){return string.split(/\r?\n/);};
|
||||
CodeMirror.splitLines = splitLines;
|
||||
|
||||
var hasSelection = window.getSelection ? function(te) {
|
||||
try { return te.selectionStart != te.selectionEnd; }
|
||||
catch(e) { return false; }
|
||||
} : function(te) {
|
||||
try {var range = te.ownerDocument.selection.createRange();}
|
||||
catch(e) {}
|
||||
if (!range || range.parentElement() != te) return false;
|
||||
return range.compareEndPoints("StartToEnd", range) != 0;
|
||||
};
|
||||
|
||||
CodeMirror.defineMode("null", function() {
|
||||
return {token: function(stream) {stream.skipToEnd();}};
|
||||
});
|
||||
CodeMirror.defineMIME("text/plain", "null");
|
||||
|
||||
var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
|
||||
19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
|
||||
36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
|
||||
46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 127: "Delete", 186: ";", 187: "=", 188: ",",
|
||||
189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 63276: "PageUp",
|
||||
63277: "PageDown", 63275: "End", 63273: "Home", 63234: "Left", 63232: "Up", 63235: "Right",
|
||||
63233: "Down", 63302: "Insert", 63272: "Delete"};
|
||||
CodeMirror.keyNames = keyNames;
|
||||
(function() {
|
||||
// Number keys
|
||||
for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
|
||||
// Alphabetic keys
|
||||
for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
|
||||
// Function keys
|
||||
for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
|
||||
})();
|
||||
|
||||
return CodeMirror;
|
||||
})();
|
||||
373
lms/static/js/vendor/CodeMirror/mitx_markdown.js
vendored
Normal file
373
lms/static/js/vendor/CodeMirror/mitx_markdown.js
vendored
Normal file
@@ -0,0 +1,373 @@
|
||||
var schematic_height = 480;
|
||||
var schematic_width = 640;
|
||||
|
||||
$(function(){
|
||||
$(document).ready(function() {
|
||||
//$("a[rel*=leanModal]").leanModal(); //TODO: Make this work with the new modal library. Try and integrate this with the "slices"
|
||||
|
||||
$("body").append('<div id="circuit_editor" class="leanModal_box" style="z-index: 11000; left: 50%; margin-left: -250px; position: absolute; top: 100px; opacity: 1; "><div align="center">'+
|
||||
'<input class="schematic" height="' + schematic_height + '" width="' + schematic_width + '" id="schematic_editor" name="schematic" type="hidden" value=""/>' +
|
||||
'<button type="button" id="circuit_save_btn">save</button></div></div>');
|
||||
|
||||
//This is the editor that pops up as a modal
|
||||
var editorCircuit = $("#schematic_editor").get(0);
|
||||
//This is the circuit that they last clicked. The one being edited.
|
||||
var editingCircuit = null;
|
||||
//Notice we use live, because new circuits can be inserted
|
||||
$(".schematic_open").live("click", function() {
|
||||
//Find the new editingCircuit. Transfer its contents to the editorCircuit
|
||||
editingCircuit = $(this).children("input.schematic").get(0);
|
||||
|
||||
editingCircuit.schematic.update_value();
|
||||
var circuit_so_far = $(editingCircuit).val();
|
||||
|
||||
var n = editorCircuit.schematic.components.length;
|
||||
for (var i = 0; i < n; i++)
|
||||
editorCircuit.schematic.components[n - 1 - i].remove();
|
||||
|
||||
editorCircuit.schematic.load_schematic(circuit_so_far, "");
|
||||
});
|
||||
|
||||
$("#circuit_save_btn").click(function () {
|
||||
//Take the circuit from the editor and put it back into editingCircuit
|
||||
editorCircuit.schematic.update_value();
|
||||
var saving_circuit = $(editorCircuit).val();
|
||||
|
||||
var n = editingCircuit.schematic.components.length;
|
||||
for (var i = 0; i < n; i++)
|
||||
editingCircuit.schematic.components[n - 1 - i].remove();
|
||||
|
||||
editingCircuit.schematic.load_schematic(saving_circuit, "");
|
||||
|
||||
if (editingCircuit.codeMirrorLine) {
|
||||
editingCircuit.codeMirrorLine.replace(0, null, "circuit-schematic:" + saving_circuit);
|
||||
}
|
||||
|
||||
$(".modal_close").first().click();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
CodeMirror.defineMode("mitx_markdown", function(cmCfg, modeCfg) {
|
||||
|
||||
var htmlMode = CodeMirror.getMode(cmCfg, { name: 'xml', htmlMode: true });
|
||||
|
||||
var header = 'header'
|
||||
, code = 'comment'
|
||||
, quote = 'quote'
|
||||
, list = 'string'
|
||||
, hr = 'hr'
|
||||
, linktext = 'link'
|
||||
, linkhref = 'string'
|
||||
, em = 'em'
|
||||
, strong = 'strong'
|
||||
, emstrong = 'emstrong';
|
||||
|
||||
function escapeHtml(unsafe) {
|
||||
return unsafe
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
var circuit_formatter = {
|
||||
creator: function(text) {
|
||||
var circuit_value = text.match(circuitRE)[1]
|
||||
|
||||
circuit_value = escapeHtml(circuit_value);
|
||||
|
||||
var html = "<div style='display:block;line-height:0;' class='schematic_container'><a href='#circuit_editor' rel='leanModal' class='schematic_open' style='display:inline-block;'>" +
|
||||
"<input type='hidden' parts='' value='" + circuit_value + "' width='" + schematic_width + "' height='" + schematic_height + "' analyses='' class='schematic ctrls'/></a></div>";
|
||||
|
||||
return html;
|
||||
},
|
||||
size: function(text) {
|
||||
return {width: schematic_width, height:schematic_height};
|
||||
},
|
||||
callback: function(node, line) {
|
||||
try {
|
||||
update_schematics();
|
||||
var schmInput = node.firstChild.firstChild;
|
||||
schmInput.codeMirrorLine = line;
|
||||
if (schmInput.schematic) { //This is undefined if there was an error making the schematic
|
||||
schmInput.schematic.canvas.style.display = "block"; //Otherwise, it gets line height and is a weird size
|
||||
schmInput.schematic.always_draw_grid = true;
|
||||
schmInput.schematic.redraw_background();
|
||||
}
|
||||
$(node.firstChild).leanModal();
|
||||
} catch (err) {
|
||||
console.log("Error in mitx_markdown callback: " + err);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var hrRE = /^[*-=_]/
|
||||
, ulRE = /^[*-+]\s+/
|
||||
, olRE = /^[0-9]+\.\s+/
|
||||
, headerRE = /^(?:\={3,}|-{3,})$/
|
||||
, codeRE = /^(k:\t|\s{4,})/
|
||||
, textRE = /^[^\[*_\\<>`]+/
|
||||
, circuitRE = /^circuit-schematic:(.*)$/;
|
||||
|
||||
function switchInline(stream, state, f) {
|
||||
state.f = state.inline = f;
|
||||
return f(stream, state);
|
||||
}
|
||||
|
||||
function switchBlock(stream, state, f) {
|
||||
state.f = state.block = f;
|
||||
return f(stream, state);
|
||||
}
|
||||
|
||||
|
||||
// Blocks
|
||||
|
||||
function blockNormal(stream, state) {
|
||||
var match;
|
||||
if (stream.match(circuitRE)) {
|
||||
stream.skipToEnd();
|
||||
return circuit_formatter;
|
||||
} else if (stream.match(codeRE)) {
|
||||
stream.skipToEnd();
|
||||
return code;
|
||||
} else if (stream.eatSpace()) {
|
||||
return null;
|
||||
} else if (stream.peek() === '#' || stream.match(headerRE)) {
|
||||
state.header = true;
|
||||
} else if (stream.eat('>')) {
|
||||
state.indentation++;
|
||||
state.quote = true;
|
||||
} else if (stream.peek() === '[') {
|
||||
return switchInline(stream, state, footnoteLink);
|
||||
} else if (hrRE.test(stream.peek())) {
|
||||
var re = new RegExp('(?:\s*['+stream.peek()+']){3,}$');
|
||||
if (stream.match(re, true)) {
|
||||
return hr;
|
||||
}
|
||||
} else if (match = stream.match(ulRE, true) || stream.match(olRE, true)) {
|
||||
state.indentation += match[0].length;
|
||||
return list;
|
||||
}
|
||||
|
||||
return switchInline(stream, state, state.inline);
|
||||
}
|
||||
|
||||
function htmlBlock(stream, state) {
|
||||
var style = htmlMode.token(stream, state.htmlState);
|
||||
if (style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) {
|
||||
state.f = inlineNormal;
|
||||
state.block = blockNormal;
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
|
||||
// Inline
|
||||
function getType(state) {
|
||||
|
||||
// Set defaults
|
||||
returnValue = '';
|
||||
|
||||
// Strong / Emphasis
|
||||
if(state.strong){
|
||||
if(state.em){
|
||||
returnValue += (returnValue ? ' ' : '') + emstrong;
|
||||
} else {
|
||||
returnValue += (returnValue ? ' ' : '') + strong;
|
||||
}
|
||||
} else {
|
||||
if(state.em){
|
||||
returnValue += (returnValue ? ' ' : '') + em;
|
||||
}
|
||||
}
|
||||
|
||||
// Header
|
||||
if(state.header){
|
||||
returnValue += (returnValue ? ' ' : '') + header;
|
||||
}
|
||||
|
||||
// Quotes
|
||||
if(state.quote){
|
||||
returnValue += (returnValue ? ' ' : '') + quote;
|
||||
}
|
||||
|
||||
// Check valud and return
|
||||
if(!returnValue){
|
||||
returnValue = null;
|
||||
}
|
||||
return returnValue;
|
||||
|
||||
}
|
||||
|
||||
function handleText(stream, state) {
|
||||
if (stream.match(textRE, true)) {
|
||||
return getType(state);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function inlineNormal(stream, state) {
|
||||
var style = state.text(stream, state)
|
||||
if (typeof style !== 'undefined')
|
||||
return style;
|
||||
|
||||
var ch = stream.next();
|
||||
|
||||
if (ch === '\\') {
|
||||
stream.next();
|
||||
return getType(state);
|
||||
}
|
||||
if (ch === '`') {
|
||||
return switchInline(stream, state, inlineElement(code, '`'));
|
||||
}
|
||||
if (ch === '[') {
|
||||
return switchInline(stream, state, linkText);
|
||||
}
|
||||
if (ch === '<' && stream.match(/^\w/, false)) {
|
||||
stream.backUp(1);
|
||||
return switchBlock(stream, state, htmlBlock);
|
||||
}
|
||||
|
||||
var t = getType(state);
|
||||
if (ch === '*' || ch === '_') {
|
||||
if (stream.eat(ch)) {
|
||||
return (state.strong = !state.strong) ? getType(state) : t;
|
||||
}
|
||||
return (state.em = !state.em) ? getType(state) : t;
|
||||
}
|
||||
|
||||
return getType(state);
|
||||
}
|
||||
|
||||
function linkText(stream, state) {
|
||||
while (!stream.eol()) {
|
||||
var ch = stream.next();
|
||||
if (ch === '\\') stream.next();
|
||||
if (ch === ']') {
|
||||
state.inline = state.f = linkHref;
|
||||
return linktext;
|
||||
}
|
||||
}
|
||||
return linktext;
|
||||
}
|
||||
|
||||
function linkHref(stream, state) {
|
||||
stream.eatSpace();
|
||||
var ch = stream.next();
|
||||
if (ch === '(' || ch === '[') {
|
||||
return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']'));
|
||||
}
|
||||
return 'error';
|
||||
}
|
||||
|
||||
function footnoteLink(stream, state) {
|
||||
if (stream.match(/^[^\]]*\]:/, true)) {
|
||||
state.f = footnoteUrl;
|
||||
return linktext;
|
||||
}
|
||||
return switchInline(stream, state, inlineNormal);
|
||||
}
|
||||
|
||||
function footnoteUrl(stream, state) {
|
||||
stream.eatSpace();
|
||||
stream.match(/^[^\s]+/, true);
|
||||
state.f = state.inline = inlineNormal;
|
||||
return linkhref;
|
||||
}
|
||||
|
||||
function inlineRE(endChar) {
|
||||
if (!inlineRE[endChar]) {
|
||||
// match any not-escaped-non-endChar and any escaped char
|
||||
// then match endChar or eol
|
||||
inlineRE[endChar] = new RegExp('^(?:[^\\\\\\' + endChar + ']|\\\\.)*(?:\\' + endChar + '|$)');
|
||||
}
|
||||
return inlineRE[endChar];
|
||||
}
|
||||
|
||||
function inlineElement(type, endChar, next) {
|
||||
next = next || inlineNormal;
|
||||
return function(stream, state) {
|
||||
stream.match(inlineRE(endChar));
|
||||
state.inline = state.f = next;
|
||||
return type;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
startState: function() {
|
||||
return {
|
||||
f: blockNormal,
|
||||
|
||||
block: blockNormal,
|
||||
htmlState: htmlMode.startState(),
|
||||
indentation: 0,
|
||||
|
||||
inline: inlineNormal,
|
||||
text: handleText,
|
||||
em: false,
|
||||
strong: false,
|
||||
header: false,
|
||||
quote: false
|
||||
};
|
||||
},
|
||||
|
||||
copyState: function(s) {
|
||||
return {
|
||||
f: s.f,
|
||||
|
||||
block: s.block,
|
||||
htmlState: CodeMirror.copyState(htmlMode, s.htmlState),
|
||||
indentation: s.indentation,
|
||||
|
||||
inline: s.inline,
|
||||
text: s.text,
|
||||
em: s.em,
|
||||
strong: s.strong,
|
||||
header: s.header,
|
||||
quote: s.quote
|
||||
};
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
if (stream.sol()) {
|
||||
// Reset EM state
|
||||
state.em = false;
|
||||
// Reset STRONG state
|
||||
state.strong = false;
|
||||
// Reset state.header
|
||||
state.header = false;
|
||||
// Reset state.quote
|
||||
state.quote = false;
|
||||
|
||||
state.f = state.block;
|
||||
var previousIndentation = state.indentation
|
||||
, currentIndentation = 0;
|
||||
while (previousIndentation > 0) {
|
||||
if (stream.eat(' ')) {
|
||||
previousIndentation--;
|
||||
currentIndentation++;
|
||||
} else if (previousIndentation >= 4 && stream.eat('\t')) {
|
||||
previousIndentation -= 4;
|
||||
currentIndentation += 4;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
state.indentation = currentIndentation;
|
||||
|
||||
if (currentIndentation > 0) return null;
|
||||
}
|
||||
return state.f(stream, state);
|
||||
},
|
||||
|
||||
getType: getType
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
CodeMirror.defineMIME("text/x-markdown", "markdown");
|
||||
341
lms/static/js/vendor/CodeMirror/python.js
vendored
Normal file
341
lms/static/js/vendor/CodeMirror/python.js
vendored
Normal file
@@ -0,0 +1,341 @@
|
||||
CodeMirror.defineMode("python", function(conf, parserConf) {
|
||||
var ERRORCLASS = 'error';
|
||||
|
||||
function wordRegexp(words) {
|
||||
return new RegExp("^((" + words.join(")|(") + "))\\b");
|
||||
}
|
||||
|
||||
var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
|
||||
var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
|
||||
var doubleOperators = new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
|
||||
var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
|
||||
var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
|
||||
var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
|
||||
|
||||
var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']);
|
||||
var commonkeywords = ['as', 'assert', 'break', 'class', 'continue',
|
||||
'def', 'del', 'elif', 'else', 'except', 'finally',
|
||||
'for', 'from', 'global', 'if', 'import',
|
||||
'lambda', 'pass', 'raise', 'return',
|
||||
'try', 'while', 'with', 'yield'];
|
||||
var commonBuiltins = ['abs', 'all', 'any', 'bin', 'bool', 'bytearray', 'callable', 'chr',
|
||||
'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod',
|
||||
'enumerate', 'eval', 'filter', 'float', 'format', 'frozenset',
|
||||
'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id',
|
||||
'input', 'int', 'isinstance', 'issubclass', 'iter', 'len',
|
||||
'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next',
|
||||
'object', 'oct', 'open', 'ord', 'pow', 'property', 'range',
|
||||
'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
|
||||
'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple',
|
||||
'type', 'vars', 'zip', '__import__', 'NotImplemented',
|
||||
'Ellipsis', '__debug__'];
|
||||
var py2 = {'builtins': ['apply', 'basestring', 'buffer', 'cmp', 'coerce', 'execfile',
|
||||
'file', 'intern', 'long', 'raw_input', 'reduce', 'reload',
|
||||
'unichr', 'unicode', 'xrange', 'False', 'True', 'None'],
|
||||
'keywords': ['exec', 'print']};
|
||||
var py3 = {'builtins': ['ascii', 'bytes', 'exec', 'print'],
|
||||
'keywords': ['nonlocal', 'False', 'True', 'None']};
|
||||
|
||||
if (!!parserConf.version && parseInt(parserConf.version, 10) === 3) {
|
||||
commonkeywords = commonkeywords.concat(py3.keywords);
|
||||
commonBuiltins = commonBuiltins.concat(py3.builtins);
|
||||
var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i");
|
||||
} else {
|
||||
commonkeywords = commonkeywords.concat(py2.keywords);
|
||||
commonBuiltins = commonBuiltins.concat(py2.builtins);
|
||||
var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
|
||||
}
|
||||
var keywords = wordRegexp(commonkeywords);
|
||||
var builtins = wordRegexp(commonBuiltins);
|
||||
|
||||
var indentInfo = null;
|
||||
|
||||
// tokenizers
|
||||
function tokenBase(stream, state) {
|
||||
// Handle scope changes
|
||||
if (stream.sol()) {
|
||||
var scopeOffset = state.scopes[0].offset;
|
||||
if (stream.eatSpace()) {
|
||||
var lineOffset = stream.indentation();
|
||||
if (lineOffset > scopeOffset) {
|
||||
indentInfo = 'indent';
|
||||
} else if (lineOffset < scopeOffset) {
|
||||
indentInfo = 'dedent';
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
if (scopeOffset > 0) {
|
||||
dedent(stream, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stream.eatSpace()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var ch = stream.peek();
|
||||
|
||||
// Handle Comments
|
||||
if (ch === '#') {
|
||||
stream.skipToEnd();
|
||||
return 'comment';
|
||||
}
|
||||
|
||||
// Handle Number Literals
|
||||
if (stream.match(/^[0-9\.]/, false)) {
|
||||
var floatLiteral = false;
|
||||
// Floats
|
||||
if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; }
|
||||
if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; }
|
||||
if (stream.match(/^\.\d+/)) { floatLiteral = true; }
|
||||
if (floatLiteral) {
|
||||
// Float literals may be "imaginary"
|
||||
stream.eat(/J/i);
|
||||
return 'number';
|
||||
}
|
||||
// Integers
|
||||
var intLiteral = false;
|
||||
// Hex
|
||||
if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true; }
|
||||
// Binary
|
||||
if (stream.match(/^0b[01]+/i)) { intLiteral = true; }
|
||||
// Octal
|
||||
if (stream.match(/^0o[0-7]+/i)) { intLiteral = true; }
|
||||
// Decimal
|
||||
if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
|
||||
// Decimal literals may be "imaginary"
|
||||
stream.eat(/J/i);
|
||||
// TODO - Can you have imaginary longs?
|
||||
intLiteral = true;
|
||||
}
|
||||
// Zero by itself with no other piece of number.
|
||||
if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
|
||||
if (intLiteral) {
|
||||
// Integer literals may be "long"
|
||||
stream.eat(/L/i);
|
||||
return 'number';
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Strings
|
||||
if (stream.match(stringPrefixes)) {
|
||||
state.tokenize = tokenStringFactory(stream.current());
|
||||
return state.tokenize(stream, state);
|
||||
}
|
||||
|
||||
// Handle operators and Delimiters
|
||||
if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
|
||||
return null;
|
||||
}
|
||||
if (stream.match(doubleOperators)
|
||||
|| stream.match(singleOperators)
|
||||
|| stream.match(wordOperators)) {
|
||||
return 'operator';
|
||||
}
|
||||
if (stream.match(singleDelimiters)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (stream.match(keywords)) {
|
||||
return 'keyword';
|
||||
}
|
||||
|
||||
if (stream.match(builtins)) {
|
||||
return 'builtin';
|
||||
}
|
||||
|
||||
if (stream.match(identifiers)) {
|
||||
return 'variable';
|
||||
}
|
||||
|
||||
// Handle non-detected items
|
||||
stream.next();
|
||||
return ERRORCLASS;
|
||||
}
|
||||
|
||||
function tokenStringFactory(delimiter) {
|
||||
while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) {
|
||||
delimiter = delimiter.substr(1);
|
||||
}
|
||||
var singleline = delimiter.length == 1;
|
||||
var OUTCLASS = 'string';
|
||||
|
||||
return function tokenString(stream, state) {
|
||||
while (!stream.eol()) {
|
||||
stream.eatWhile(/[^'"\\]/);
|
||||
if (stream.eat('\\')) {
|
||||
stream.next();
|
||||
if (singleline && stream.eol()) {
|
||||
return OUTCLASS;
|
||||
}
|
||||
} else if (stream.match(delimiter)) {
|
||||
state.tokenize = tokenBase;
|
||||
return OUTCLASS;
|
||||
} else {
|
||||
stream.eat(/['"]/);
|
||||
}
|
||||
}
|
||||
if (singleline) {
|
||||
if (parserConf.singleLineStringErrors) {
|
||||
return ERRORCLASS;
|
||||
} else {
|
||||
state.tokenize = tokenBase;
|
||||
}
|
||||
}
|
||||
return OUTCLASS;
|
||||
};
|
||||
}
|
||||
|
||||
function indent(stream, state, type) {
|
||||
type = type || 'py';
|
||||
var indentUnit = 0;
|
||||
if (type === 'py') {
|
||||
if (state.scopes[0].type !== 'py') {
|
||||
state.scopes[0].offset = stream.indentation();
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < state.scopes.length; ++i) {
|
||||
if (state.scopes[i].type === 'py') {
|
||||
indentUnit = state.scopes[i].offset + conf.indentUnit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
indentUnit = stream.column() + stream.current().length;
|
||||
}
|
||||
state.scopes.unshift({
|
||||
offset: indentUnit,
|
||||
type: type
|
||||
});
|
||||
}
|
||||
|
||||
function dedent(stream, state, type) {
|
||||
type = type || 'py';
|
||||
if (state.scopes.length == 1) return;
|
||||
if (state.scopes[0].type === 'py') {
|
||||
var _indent = stream.indentation();
|
||||
var _indent_index = -1;
|
||||
for (var i = 0; i < state.scopes.length; ++i) {
|
||||
if (_indent === state.scopes[i].offset) {
|
||||
_indent_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_indent_index === -1) {
|
||||
return true;
|
||||
}
|
||||
while (state.scopes[0].offset !== _indent) {
|
||||
state.scopes.shift();
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
if (type === 'py') {
|
||||
state.scopes[0].offset = stream.indentation();
|
||||
return false;
|
||||
} else {
|
||||
if (state.scopes[0].type != type) {
|
||||
return true;
|
||||
}
|
||||
state.scopes.shift();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tokenLexer(stream, state) {
|
||||
indentInfo = null;
|
||||
var style = state.tokenize(stream, state);
|
||||
var current = stream.current();
|
||||
|
||||
// Handle '.' connected identifiers
|
||||
if (current === '.') {
|
||||
style = state.tokenize(stream, state);
|
||||
current = stream.current();
|
||||
if (style === 'variable' || style === 'builtin') {
|
||||
return 'variable';
|
||||
} else {
|
||||
return ERRORCLASS;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle decorators
|
||||
if (current === '@') {
|
||||
style = state.tokenize(stream, state);
|
||||
current = stream.current();
|
||||
if (style === 'variable'
|
||||
|| current === '@staticmethod'
|
||||
|| current === '@classmethod') {
|
||||
return 'meta';
|
||||
} else {
|
||||
return ERRORCLASS;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle scope changes.
|
||||
if (current === 'pass' || current === 'return') {
|
||||
state.dedent += 1;
|
||||
}
|
||||
if (current === 'lambda') state.lambda = true;
|
||||
if ((current === ':' && !state.lambda && state.scopes[0].type == 'py')
|
||||
|| indentInfo === 'indent') {
|
||||
indent(stream, state);
|
||||
}
|
||||
var delimiter_index = '[({'.indexOf(current);
|
||||
if (delimiter_index !== -1) {
|
||||
indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1));
|
||||
}
|
||||
if (indentInfo === 'dedent') {
|
||||
if (dedent(stream, state)) {
|
||||
return ERRORCLASS;
|
||||
}
|
||||
}
|
||||
delimiter_index = '])}'.indexOf(current);
|
||||
if (delimiter_index !== -1) {
|
||||
if (dedent(stream, state, current)) {
|
||||
return ERRORCLASS;
|
||||
}
|
||||
}
|
||||
if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'py') {
|
||||
if (state.scopes.length > 1) state.scopes.shift();
|
||||
state.dedent -= 1;
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
var external = {
|
||||
startState: function(basecolumn) {
|
||||
return {
|
||||
tokenize: tokenBase,
|
||||
scopes: [{offset:basecolumn || 0, type:'py'}],
|
||||
lastToken: null,
|
||||
lambda: false,
|
||||
dedent: 0
|
||||
};
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
var style = tokenLexer(stream, state);
|
||||
|
||||
state.lastToken = {style:style, content: stream.current()};
|
||||
|
||||
if (stream.eol() && stream.lambda) {
|
||||
state.lambda = false;
|
||||
}
|
||||
|
||||
return style;
|
||||
},
|
||||
|
||||
indent: function(state, textAfter) {
|
||||
if (state.tokenize != tokenBase) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return state.scopes[0].offset;
|
||||
}
|
||||
|
||||
};
|
||||
return external;
|
||||
});
|
||||
|
||||
CodeMirror.defineMIME("text/x-python", "python");
|
||||
267
lms/static/js/vendor/CodeMirror/xml.js
vendored
Normal file
267
lms/static/js/vendor/CodeMirror/xml.js
vendored
Normal file
@@ -0,0 +1,267 @@
|
||||
CodeMirror.defineMode("xml", function(config, parserConfig) {
|
||||
var indentUnit = config.indentUnit;
|
||||
var Kludges = parserConfig.htmlMode ? {
|
||||
autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
|
||||
"meta": true, "col": true, "frame": true, "base": true, "area": true},
|
||||
doNotIndent: {"pre": true},
|
||||
allowUnquoted: true,
|
||||
allowMissing: false
|
||||
} : {autoSelfClosers: {}, doNotIndent: {}, allowUnquoted: false, allowMissing: false};
|
||||
var alignCDATA = parserConfig.alignCDATA;
|
||||
|
||||
// Return variables for tokenizers
|
||||
var tagName, type;
|
||||
|
||||
function inText(stream, state) {
|
||||
function chain(parser) {
|
||||
state.tokenize = parser;
|
||||
return parser(stream, state);
|
||||
}
|
||||
|
||||
var ch = stream.next();
|
||||
if (ch == "<") {
|
||||
if (stream.eat("!")) {
|
||||
if (stream.eat("[")) {
|
||||
if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
|
||||
else return null;
|
||||
}
|
||||
else if (stream.match("--")) return chain(inBlock("comment", "-->"));
|
||||
else if (stream.match("DOCTYPE", true, true)) {
|
||||
stream.eatWhile(/[\w\._\-]/);
|
||||
return chain(doctype(1));
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
else if (stream.eat("?")) {
|
||||
stream.eatWhile(/[\w\._\-]/);
|
||||
state.tokenize = inBlock("meta", "?>");
|
||||
return "meta";
|
||||
}
|
||||
else {
|
||||
type = stream.eat("/") ? "closeTag" : "openTag";
|
||||
stream.eatSpace();
|
||||
tagName = "";
|
||||
var c;
|
||||
while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
|
||||
state.tokenize = inTag;
|
||||
return "tag";
|
||||
}
|
||||
}
|
||||
else if (ch == "&") {
|
||||
var ok;
|
||||
if (stream.eat("#")) {
|
||||
if (stream.eat("x")) {
|
||||
ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
|
||||
} else {
|
||||
ok = stream.eatWhile(/[\d]/) && stream.eat(";");
|
||||
}
|
||||
} else {
|
||||
ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
|
||||
}
|
||||
return ok ? "atom" : "error";
|
||||
}
|
||||
else {
|
||||
stream.eatWhile(/[^&<]/);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function inTag(stream, state) {
|
||||
var ch = stream.next();
|
||||
if (ch == ">" || (ch == "/" && stream.eat(">"))) {
|
||||
state.tokenize = inText;
|
||||
type = ch == ">" ? "endTag" : "selfcloseTag";
|
||||
return "tag";
|
||||
}
|
||||
else if (ch == "=") {
|
||||
type = "equals";
|
||||
return null;
|
||||
}
|
||||
else if (/[\'\"]/.test(ch)) {
|
||||
state.tokenize = inAttribute(ch);
|
||||
return state.tokenize(stream, state);
|
||||
}
|
||||
else {
|
||||
stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/);
|
||||
return "word";
|
||||
}
|
||||
}
|
||||
|
||||
function inAttribute(quote) {
|
||||
return function(stream, state) {
|
||||
while (!stream.eol()) {
|
||||
if (stream.next() == quote) {
|
||||
state.tokenize = inTag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return "string";
|
||||
};
|
||||
}
|
||||
|
||||
function inBlock(style, terminator) {
|
||||
return function(stream, state) {
|
||||
while (!stream.eol()) {
|
||||
if (stream.match(terminator)) {
|
||||
state.tokenize = inText;
|
||||
break;
|
||||
}
|
||||
stream.next();
|
||||
}
|
||||
return style;
|
||||
};
|
||||
}
|
||||
function doctype(depth) {
|
||||
return function(stream, state) {
|
||||
var ch;
|
||||
while ((ch = stream.next()) != null) {
|
||||
if (ch == "<") {
|
||||
state.tokenize = doctype(depth + 1);
|
||||
return state.tokenize(stream, state);
|
||||
} else if (ch == ">") {
|
||||
if (depth == 1) {
|
||||
state.tokenize = inText;
|
||||
break;
|
||||
} else {
|
||||
state.tokenize = doctype(depth - 1);
|
||||
return state.tokenize(stream, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
return "meta";
|
||||
};
|
||||
}
|
||||
|
||||
var curState, setStyle;
|
||||
function pass() {
|
||||
for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
|
||||
}
|
||||
function cont() {
|
||||
pass.apply(null, arguments);
|
||||
return true;
|
||||
}
|
||||
|
||||
function pushContext(tagName, startOfLine) {
|
||||
var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
|
||||
curState.context = {
|
||||
prev: curState.context,
|
||||
tagName: tagName,
|
||||
indent: curState.indented,
|
||||
startOfLine: startOfLine,
|
||||
noIndent: noIndent
|
||||
};
|
||||
}
|
||||
function popContext() {
|
||||
if (curState.context) curState.context = curState.context.prev;
|
||||
}
|
||||
|
||||
function element(type) {
|
||||
if (type == "openTag") {
|
||||
curState.tagName = tagName;
|
||||
return cont(attributes, endtag(curState.startOfLine));
|
||||
} else if (type == "closeTag") {
|
||||
var err = false;
|
||||
if (curState.context) {
|
||||
err = curState.context.tagName != tagName;
|
||||
} else {
|
||||
err = true;
|
||||
}
|
||||
if (err) setStyle = "error";
|
||||
return cont(endclosetag(err));
|
||||
}
|
||||
return cont();
|
||||
}
|
||||
function endtag(startOfLine) {
|
||||
return function(type) {
|
||||
if (type == "selfcloseTag" ||
|
||||
(type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase())))
|
||||
return cont();
|
||||
if (type == "endTag") {pushContext(curState.tagName, startOfLine); return cont();}
|
||||
return cont();
|
||||
};
|
||||
}
|
||||
function endclosetag(err) {
|
||||
return function(type) {
|
||||
if (err) setStyle = "error";
|
||||
if (type == "endTag") { popContext(); return cont(); }
|
||||
setStyle = "error";
|
||||
return cont(arguments.callee);
|
||||
}
|
||||
}
|
||||
|
||||
function attributes(type) {
|
||||
if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
|
||||
if (type == "endTag" || type == "selfcloseTag") return pass();
|
||||
setStyle = "error";
|
||||
return cont(attributes);
|
||||
}
|
||||
function attribute(type) {
|
||||
if (type == "equals") return cont(attvalue, attributes);
|
||||
if (!Kludges.allowMissing) setStyle = "error";
|
||||
return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
|
||||
}
|
||||
function attvalue(type) {
|
||||
if (type == "string") return cont(attvaluemaybe);
|
||||
if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
|
||||
setStyle = "error";
|
||||
return (type == "endTag" || type == "selfCloseTag") ? pass() : cont();
|
||||
}
|
||||
function attvaluemaybe(type) {
|
||||
if (type == "string") return cont(attvaluemaybe);
|
||||
else return pass();
|
||||
}
|
||||
|
||||
return {
|
||||
startState: function() {
|
||||
return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null};
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
if (stream.sol()) {
|
||||
state.startOfLine = true;
|
||||
state.indented = stream.indentation();
|
||||
}
|
||||
if (stream.eatSpace()) return null;
|
||||
|
||||
setStyle = type = tagName = null;
|
||||
var style = state.tokenize(stream, state);
|
||||
state.type = type;
|
||||
if ((style || type) && style != "comment") {
|
||||
curState = state;
|
||||
while (true) {
|
||||
var comb = state.cc.pop() || element;
|
||||
if (comb(type || style)) break;
|
||||
}
|
||||
}
|
||||
state.startOfLine = false;
|
||||
return setStyle || style;
|
||||
},
|
||||
|
||||
indent: function(state, textAfter, fullLine) {
|
||||
var context = state.context;
|
||||
if ((state.tokenize != inTag && state.tokenize != inText) ||
|
||||
context && context.noIndent)
|
||||
return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
|
||||
if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
|
||||
if (context && /^<\//.test(textAfter))
|
||||
context = context.prev;
|
||||
while (context && !context.startOfLine)
|
||||
context = context.prev;
|
||||
if (context) return context.indent + indentUnit;
|
||||
else return 0;
|
||||
},
|
||||
|
||||
compareStates: function(a, b) {
|
||||
if (a.indented != b.indented || a.tokenize != b.tokenize) return false;
|
||||
for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) {
|
||||
if (!ca || !cb) return ca == cb;
|
||||
if (ca.tagName != cb.tagName) return false;
|
||||
}
|
||||
},
|
||||
|
||||
electricChars: "/"
|
||||
};
|
||||
});
|
||||
|
||||
CodeMirror.defineMIME("application/xml", "xml");
|
||||
CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
|
||||
@@ -1,5 +1,5 @@
|
||||
form {
|
||||
font-size: 0em;
|
||||
font-size: 1em;
|
||||
|
||||
label {
|
||||
color: $base-font-color;
|
||||
|
||||
@@ -17,7 +17,7 @@ def url_class(url):
|
||||
<li class="book"><a href="${reverse('book', args=[course.id])}" class="${url_class('book')}">Textbook</a></li>
|
||||
<li class="discussion"><a href="${reverse('questions')}">Discussion</a></li>
|
||||
% endif
|
||||
<li class="wiki"><a href="${reverse('wiki_root')}" class="${url_class('wiki')}">Wiki</a></li>
|
||||
<li class="wiki"><a href="${reverse('wiki_root', args=[course.id])}" class="${url_class('wiki')}">Wiki</a></li>
|
||||
% if user.is_authenticated():
|
||||
<li class="profile"><a href="${reverse('profile', args=[course.id])}" class="${url_class('profile')}">Profile</a></li>
|
||||
% endif
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
##This file is based on the template from the SimpleWiki source which carries the GPL license
|
||||
|
||||
<%inherit file="main.html"/>
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%inherit file="../main.html"/>
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
|
||||
<%!
|
||||
from django.core.urlresolvers import reverse
|
||||
from simplewiki.views import wiki_reverse
|
||||
%>
|
||||
|
||||
<%block name="headextra">
|
||||
<script type="text/javascript" src="${static.url('js/simplewiki/bsn.AutoSuggest_c_2.0.js')}"></script>
|
||||
|
||||
<%!
|
||||
from django.core.urlresolvers import reverse
|
||||
%>
|
||||
<script type="text/javascript" src="${static.url('js/simplewiki-AutoSuggest_c_2.0.js')}"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
function set_related_article_id(s) {
|
||||
@@ -19,7 +20,7 @@
|
||||
var x = window.onload;
|
||||
window.onload = function(){
|
||||
var options = {
|
||||
script: "${reverse("search_related", args=[wiki_article.get_url()] )}/?self=${wiki_article.pk}&",
|
||||
script: "${ wiki_reverse('search_related', wiki_article, course)}/?self=${wiki_article.pk}&",
|
||||
json: true,
|
||||
varname: "query",
|
||||
maxresults: 35,
|
||||
@@ -69,23 +70,19 @@
|
||||
|
||||
<%block name="bodyextra">
|
||||
|
||||
<%include file="course_navigation.html" args="active_page='wiki'" />
|
||||
%if course:
|
||||
<%include file="../course_navigation.html" args="active_page='wiki'" />
|
||||
%endif
|
||||
|
||||
<section class="main-content">
|
||||
<div class="wiki-wrapper">
|
||||
<%block name="wiki_panel">
|
||||
<div aria-label="Wiki Navigation" id="wiki_panel">
|
||||
<h2>Course Wiki</h2>
|
||||
<%
|
||||
if (wiki_article is not UNDEFINED):
|
||||
baseURL = reverse("wiki_view", args=[wiki_article.get_url()])
|
||||
else:
|
||||
baseURL = reverse("wiki_view", args=["/"])
|
||||
%>
|
||||
<ul class="action">
|
||||
<li>
|
||||
<h3>
|
||||
<a href="${reverse("wiki_list_articles", args=[])}">All Articles</a>
|
||||
<a href="${wiki_reverse("wiki_list_articles", course=course, namespace=namespace)}">All Articles</a>
|
||||
</h3>
|
||||
</li>
|
||||
|
||||
@@ -96,36 +93,25 @@
|
||||
|
||||
<div id="wiki_create_form">
|
||||
<%
|
||||
theaction = "this.wiki_article_name.value.replace(/([^a-zA-Z0-9\-])/g, '')"
|
||||
baseURL = reverse("wiki_create", args=["/"])
|
||||
baseURL = wiki_reverse("wiki_create", course=course, kwargs={"article_path" : namespace + "/" })
|
||||
%>
|
||||
|
||||
<form method="GET" onsubmit="this.action='${baseURL + "' + " + theaction};">
|
||||
|
||||
<form method="GET" onsubmit="this.action='${baseURL}' + this.wiki_article_name.value.replace(/([^a-zA-Z0-9\-])/g, '');">
|
||||
<div>
|
||||
<label for="id_wiki_article_name">Title of article</label>
|
||||
<input type="text" name="wiki_article_name" id="id_wiki_article_name" /><br/>
|
||||
<!-- <label for="id_wiki_article_is_child">Create as a child of current article</label> -->
|
||||
<!-- <input type="checkbox" name="wiki_article_is_child" id="id_wiki_artcile_is_child" disabled="true" ${ 'checked="checked"' if wiki_article is not UNDEFINED else ""}> -->
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
|
||||
<li>
|
||||
<input type="submit" class="button" value="Create" style="display: inline-block; margin-right: 2px; font-weight: bold;" />
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="search">
|
||||
<form method="POST" action='${reverse("wiki_search_articles", args=[])}'>
|
||||
<form method="GET" action='${wiki_reverse("wiki_search_articles", course=course, namespace=namespace)}'>
|
||||
<label class="wiki_box_title">Search</label>
|
||||
<div style="display:none">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}"/>
|
||||
</div>
|
||||
<input type="text" placeholder="Search" name="value" id="wiki_search_input" style="width: 71%" value="${wiki_search_query if wiki_search_query is not UNDEFINED else '' |h}"/>
|
||||
<input type="submit" id="wiki_search_input_submit" value="Go!" style="width: 20%" />
|
||||
</form>
|
||||
@@ -148,15 +134,15 @@
|
||||
<ul>
|
||||
|
||||
<li>
|
||||
<input type="button" onclick="javascript:location.href='${reverse("wiki_view", args=[wiki_article.get_url()])}'" value="View" class="view" />
|
||||
<a href="${ wiki_reverse('wiki_view', wiki_article, course)}" class="view">View</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="button" onclick="javascript:location.href='${reverse("wiki_edit", args=[wiki_article.get_url()])}'" value="Edit" ${'disabled="true"' if not wiki_write else ""} class="edit"/>
|
||||
<a href="${ wiki_reverse('wiki_edit', wiki_article, course)}" class="edit">Edit</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="button" onclick="javascript:location.href='${reverse("wiki_history", args=[wiki_article.get_url(),1])}'" value="History" class="button history" />
|
||||
<a href="${ wiki_reverse('wiki_history', wiki_article, course)}" class="history">History</a>
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
77
lms/templates/simplewiki/simplewiki_edit.html
Normal file
77
lms/templates/simplewiki/simplewiki_edit.html
Normal file
@@ -0,0 +1,77 @@
|
||||
##This file is based on the template from the SimpleWiki source which carries the GPL license
|
||||
|
||||
<%inherit file="simplewiki_base.html"/>
|
||||
|
||||
<%block name="title">
|
||||
<title>
|
||||
%if create_article:
|
||||
Wiki – Create Article – MITx 6.002x
|
||||
%else:
|
||||
${"Edit " + wiki_title + " - " if wiki_title is not UNDEFINED else ""}MITx 6.002x Wiki
|
||||
%endif
|
||||
</title></%block>
|
||||
|
||||
<%block name="wiki_page_title">
|
||||
%if create_article:
|
||||
<h1>Create article</h1>
|
||||
%else:
|
||||
<h1>${ wiki_article.title }</h1>
|
||||
%endif
|
||||
</%block>
|
||||
|
||||
<%block name="wiki_head">
|
||||
<script type="text/javascript" src="${ settings.LIB_URL }vendor/CodeMirror/codemirror.js"></script>
|
||||
<link rel="stylesheet" href="${ settings.LIB_URL }vendor/CodeMirror/codemirror.css" />
|
||||
|
||||
<script type="text/javascript" src="${ settings.LIB_URL }vendor/CodeMirror/xml.js"></script>
|
||||
<script type="text/javascript" src="${ settings.LIB_URL }vendor/CodeMirror/mitx_markdown.js"></script>
|
||||
|
||||
<script>
|
||||
$(function(){
|
||||
$(document).ready(function() {
|
||||
//TODO: Re-enable this once the styling supports it
|
||||
// var editor = CodeMirror.fromTextArea(document.getElementById("id_contents"), {
|
||||
// mode: 'mitx_markdown',
|
||||
// matchBrackets: true,
|
||||
// theme: "default",
|
||||
// lineWrapping: true,
|
||||
// });
|
||||
//
|
||||
// //Store the inital contents so we can compare for unsaved changes
|
||||
// var initial_contents = editor.getValue();
|
||||
//
|
||||
// window.onbeforeunload = function askConfirm() { //Warn the user before they navigate away
|
||||
// if ( editor.getValue() != initial_contents ) {
|
||||
// return "You have made changes to the article that have not been saved yet.";
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// $("#submit_edit").click(function() {
|
||||
// initial_contents = editor.getValue();
|
||||
// });
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
</%block>
|
||||
|
||||
|
||||
<%block name="wiki_body">
|
||||
<form method="POST" id="wiki_revision">
|
||||
<div style="display:none">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}"/>
|
||||
</div>
|
||||
${wiki_form}
|
||||
%if create_article:
|
||||
<input type="submit" id="submit_edit" value="Create article" /></td>
|
||||
%else:
|
||||
<input type="submit" id="submit_edit" name="edit" value="Save Changes" />
|
||||
<input type="submit" id="submit_delete" name="delete" value="Delete article" />
|
||||
%endif
|
||||
</form>
|
||||
|
||||
<%include file="simplewiki_instructions.html"/>
|
||||
|
||||
</%block>
|
||||
@@ -3,7 +3,7 @@
|
||||
<%inherit file="simplewiki_base.html"/>
|
||||
|
||||
<%!
|
||||
from django.core.urlresolvers import reverse
|
||||
from simplewiki.views import wiki_reverse
|
||||
%>
|
||||
|
||||
<%block name="title"><title>Wiki Error – MITx 6.002x</title></%block>
|
||||
@@ -21,30 +21,17 @@ ${wiki_error}
|
||||
%endif
|
||||
|
||||
%if wiki_err_notfound is not UNDEFINED:
|
||||
%if wiki_url is not UNDEFINED:
|
||||
<p>
|
||||
The page you requested could not be found.
|
||||
Click <a href="${reverse("wiki_create", args=[wiki_url])}">here</a> to create it.
|
||||
</p>
|
||||
%else:
|
||||
<p>
|
||||
Or maybe rather: Congratulations! It seems that there's no root
|
||||
article, which is probably because you just installed simple-wiki
|
||||
and your installation is working. Now you can create the root article.
|
||||
Click <a href="{% url wiki_create "" %}">here</a> to create it.
|
||||
</p>
|
||||
%endif
|
||||
%else:
|
||||
%if wiki_err_noparent is not UNDEFINED:
|
||||
<p>
|
||||
You cannot create this page, because its parent
|
||||
does not exist. Click <a href="${reverse("wiki_create", args=[wiki_url_parent])}">here</a>
|
||||
to create it.
|
||||
The page you requested could not be found.
|
||||
Click <a href="${wiki_reverse("wiki_create", course=course, kwargs={'article_path' : article_path})}">here</a> to create it.
|
||||
</p>
|
||||
%else:
|
||||
%if wiki_err_keyword is not UNDEFINED and wiki_err_keyword:
|
||||
%elif wiki_err_no_namespace is not UNDEFINED and wiki_err_no_namespace:
|
||||
<p>
|
||||
The page you're trying to create <b>${wiki_url}</b> starts with <b>_</b>, which is reserved for internal use.
|
||||
You must specify a namespace to create an article in.
|
||||
</p>
|
||||
%elif wiki_err_bad_namespace is not UNDEFINED and wiki_err_bad_namespace:
|
||||
<p>
|
||||
The namespace for this article does not exist. This article cannot be created.
|
||||
</p>
|
||||
%elif wiki_err_locked is not UNDEFINED and wiki_err_locked:
|
||||
<p>
|
||||
@@ -75,7 +62,7 @@ ${wiki_error}
|
||||
</p>
|
||||
%elif wiki_err_deleted is not UNDEFINED and wiki_err_deleted:
|
||||
<p>
|
||||
The article you tried to access has been deleted. You may be able to restore it to an earlier version in its <a href="${reverse("wiki_history", args=[wiki_article.get_url(),1])}">history</a>, or <a href="${reverse("wiki_edit", args=[wiki_article.get_url()])}">create a new version</a>.
|
||||
The article you tried to access has been deleted. You may be able to restore it to an earlier version in its <a href="${wiki_reverse("wiki_history", wiki_article, course)}">history</a>, or <a href="${wiki_reverse("wiki_edit", wiki_article, course)}">create a new version</a>.
|
||||
</p>
|
||||
%elif wiki_err_norevision is not UNDEFINED:
|
||||
<p>
|
||||
@@ -86,8 +73,6 @@ ${wiki_error}
|
||||
An error has occured.
|
||||
</p>
|
||||
%endif
|
||||
%endif
|
||||
%endif
|
||||
|
||||
</div>
|
||||
</%block>
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
<%!
|
||||
from django.core.urlresolvers import reverse
|
||||
from simplewiki.views import wiki_reverse
|
||||
%>
|
||||
|
||||
<%block name="wiki_page_title">
|
||||
@@ -64,10 +65,10 @@ ${ wiki_article.title }
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
%if wiki_prev_page:
|
||||
<a href="${reverse("wiki_history", args=[wiki_article.get_url(), wiki_prev_page])}">Previous page</a>
|
||||
<a href="${wiki_reverse('wiki_history', wiki_article, course, kwargs={'page' : wiki_prev_page})}">Previous page</a>
|
||||
%endif
|
||||
%if wiki_next_page:
|
||||
<a href="${reverse("wiki_history", args=[wiki_article.get_url(), wiki_next_page])}">Next page</a>
|
||||
<a href="${wiki_reverse('wiki_history', wiki_article, course, kwargs={'page' : wiki_next_page})}">Next page</a>
|
||||
%endif
|
||||
</td>
|
||||
</tr>
|
||||
@@ -5,7 +5,7 @@
|
||||
<%block name="title"><title>Wiki - Revision feed - MITx 6.002x</title></%block>
|
||||
|
||||
<%!
|
||||
from django.core.urlresolvers import reverse
|
||||
from simplewiki.views import wiki_reverse
|
||||
%>
|
||||
|
||||
<%block name="wiki_page_title">
|
||||
@@ -29,7 +29,7 @@
|
||||
<% loopCount += 1 %>
|
||||
<tr style="border-top: 1px" class="${'dark ' if (loopCount % 2) == 0 else ''}${'deleted ' if (revision.deleted==2) else ''}" >
|
||||
<td width="15px">
|
||||
<a href="${reverse('wiki_view_revision',args=[revision.counter, revision.article.get_url()])}"> ${revision.article.title} - ${revision}</a>
|
||||
<a href="${wiki_reverse('wiki_view_revision', revision.article, course, kwargs={'revision_number' : revision.counter})}"> ${revision.article.title} - ${revision}</a>
|
||||
</td>
|
||||
<td>
|
||||
${ revision.revision_text if revision.revision_text else "<i>None</i>" }</td>
|
||||
@@ -50,10 +50,10 @@
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
%if wiki_prev_page:
|
||||
<a href="${reverse("wiki_revision_feed", args=[wiki_prev_page])}">Previous page</a>
|
||||
<a href="${wiki_reverse("wiki_revision_feed", course=course, namespace=namespace, kwargs={'page': wiki_prev_page})}">Previous page</a>
|
||||
%endif
|
||||
%if wiki_next_page:
|
||||
<a href="${reverse("wiki_revision_feed", args=[wiki_next_page])}">Next page</a>
|
||||
<a href="${wiki_reverse("wiki_revision_feed", course=course, namespace=namespace, kwargs={'page': wiki_next_page})}">Next page</a>
|
||||
%endif
|
||||
</td>
|
||||
</tr>
|
||||
@@ -5,7 +5,7 @@
|
||||
<%block name="title"><title>Wiki - Search Results - MITx 6.002x</title></%block>
|
||||
|
||||
<%!
|
||||
from django.core.urlresolvers import reverse
|
||||
from simplewiki.views import wiki_reverse
|
||||
%>
|
||||
|
||||
<%block name="wiki_page_title">
|
||||
@@ -23,7 +23,7 @@ Displaying all articles
|
||||
<ul class="article-list">
|
||||
%for article in wiki_search_results:
|
||||
<% article_deleted = not article.current_revision.deleted == 0 %>
|
||||
<li><h3><a href="${reverse("wiki_view", args=[article.get_url()])}">${article.title} ${'(Deleted)' if article_deleted else ''}</a></h3></li>
|
||||
<li><h3><a href="${wiki_reverse("wiki_view", article, course)}">${article.title} ${'(Deleted)' if article_deleted else ''}</a></h3></li>
|
||||
%endfor
|
||||
|
||||
%if not wiki_search_results:
|
||||
@@ -1,77 +0,0 @@
|
||||
##This file is based on the template from the SimpleWiki source which carries the GPL license
|
||||
|
||||
<%inherit file="simplewiki_base.html"/>
|
||||
|
||||
<%block name="title">
|
||||
<title>
|
||||
%if create_article:
|
||||
Wiki – Create Article – MITx 6.002x
|
||||
%else:
|
||||
${"Edit " + wiki_title + " - " if wiki_title is not UNDEFINED else ""}MITx 6.002x Wiki
|
||||
%endif
|
||||
</title></%block>
|
||||
|
||||
<%block name="wiki_page_title">
|
||||
%if create_article:
|
||||
<h1>Create article</h1>
|
||||
%else:
|
||||
<h1>${ wiki_article.title }</h1>
|
||||
%endif
|
||||
</%block>
|
||||
|
||||
<%block name="wiki_head">
|
||||
<script type="text/javascript" src="${ settings.LIB_URL }CodeMirror/codemirror.js"></script>
|
||||
<link rel="stylesheet" href="${ settings.LIB_URL }CodeMirror/codemirror.css" />
|
||||
|
||||
<script type="text/javascript" src="${ settings.LIB_URL }CodeMirror/xml.js"></script>
|
||||
<script type="text/javascript" src="${ settings.LIB_URL }CodeMirror/mitx_markdown.js"></script>
|
||||
|
||||
<script>
|
||||
$(function(){
|
||||
$(document).ready(function() {
|
||||
|
||||
var editor = CodeMirror.fromTextArea(document.getElementById("id_contents"), {
|
||||
mode: 'mitx_markdown',
|
||||
matchBrackets: true,
|
||||
theme: "default",
|
||||
lineWrapping: true,
|
||||
});
|
||||
|
||||
//Store the inital contents so we can compare for unsaved changes
|
||||
var initial_contents = editor.getValue();
|
||||
|
||||
window.onbeforeunload = function askConfirm() { //Warn the user before they navigate away
|
||||
if ( editor.getValue() != initial_contents ) {
|
||||
return "You have made changes to the article that have not been saved yet.";
|
||||
}
|
||||
};
|
||||
|
||||
$("#submit_edit").click(function() {
|
||||
initial_contents = editor.getValue();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
</%block>
|
||||
|
||||
|
||||
<%block name="wiki_body">
|
||||
<form method="POST" id="wiki_revision">
|
||||
<div style="display:none">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}"/>
|
||||
</div>
|
||||
${wiki_form}
|
||||
%if create_article:
|
||||
<input type="submit" id="submit_edit" value="Create article" /></td>
|
||||
%else:
|
||||
<input type="submit" id="submit_edit" name="edit" value="Save Changes" />
|
||||
<input type="submit" id="submit_delete" name="delete" value="Delete article" />
|
||||
%endif
|
||||
</form>
|
||||
|
||||
<%include file="simplewiki_instructions.html"/>
|
||||
|
||||
</%block>
|
||||
@@ -52,7 +52,6 @@ if settings.PERFSTATS:
|
||||
|
||||
if settings.COURSEWARE_ENABLED:
|
||||
urlpatterns += (
|
||||
url(r'^wiki/', include('simplewiki.urls')),
|
||||
url(r'^masquerade/', include('masquerade.urls')),
|
||||
url(r'^jumpto/(?P<probname>[^/]+)/$', 'courseware.views.jump_to'),
|
||||
url(r'^modx/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'),
|
||||
@@ -80,6 +79,12 @@ if settings.COURSEWARE_ENABLED:
|
||||
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/about$', 'student.views.course_info', name="about_course"),
|
||||
)
|
||||
|
||||
# Multicourse wiki
|
||||
urlpatterns += (
|
||||
url(r'^wiki/', include('simplewiki.urls')),
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/wiki/', include('simplewiki.urls')),
|
||||
)
|
||||
|
||||
if settings.ENABLE_MULTICOURSE:
|
||||
urlpatterns += (url(r'^mitxhome$', 'multicourse.views.mitxhome'),)
|
||||
|
||||
Reference in New Issue
Block a user