localsettings.py
-dev.sqlite
+*.sqlite
requirements.pybundle
*~
*.orig
/redakcja/media
/static
.sass-cache
+var
# Python garbage
*.pyc
pip install -r requirements.txt
-#. Wypełnij bazę danych (Django poprosi o utworzenie pierwszego użytkownika)::
+#. Skopiuj zawartość pliku `project/localsettings.sample` do `project/localsettings.py` i zmień go zgodnie ze swoimi potrzebami.
- ./project/manage.py syncdb
+#. Pobierz edytor::
-#. Skopiuj zawartość pliku `project/localsettings.sample` do `project/localsettings.py` i zmień go zgodnie ze swoimi potrzebami.
+ git submodule update --init
+
+#. Wypełnij bazę danych (Django poprosi o utworzenie pierwszego użytkownika)::
+
+ ./project/manage.py syncdb --migrate
#. Uruchom serwer deweloperski::
$ npm install
$ ./node_modules/.bin/mocha -u tdd $(find -name *_test.js)
-
\ No newline at end of file
+
from catalogue import models
-class BookAdmin(admin.ModelAdmin):
- list_display = ['title', 'public', '_published', '_new_publishable', 'project']
- list_filter = ['public', '_published', '_new_publishable', 'project']
- prepopulated_fields = {'slug': ['title']}
- search_fields = ['title']
-
-
-admin.site.register(models.Project)
-admin.site.register(models.Book, BookAdmin)
-admin.site.register(models.Chunk)
+admin.site.register(models.Document)
admin.site.register(models.Template)
-
-admin.site.register(models.Chunk.tag_model)
'dramat_wierszowany_lp',
'dramat_wspolczesny',
]
+
+STAGES = [
+ 'Draft',
+ 'Comments',
+ 'Comments review',
+ 'Proofreading',
+ 'Publication',
+ ]
\ No newline at end of file
# -*- coding: utf-8 -*-
from StringIO import StringIO
-from catalogue.models import Book
-from librarian import DocProvider
+#from catalogue.models import Book
+#from librarian import DocProvider
from django.http import HttpResponse
-class RedakcjaDocProvider(DocProvider):
- """Used for getting books' children."""
-
- def __init__(self, publishable):
- self.publishable = publishable
-
- def by_slug(self, slug):
- return StringIO(Book.objects.get(dc_slug=slug
- ).materialize(publishable=self.publishable
- ).encode('utf-8'))
+#~ class RedakcjaDocProvider(DocProvider):
+ #~ """Used for getting books' children."""
+#~
+ #~ def __init__(self, publishable):
+ #~ self.publishable = publishable
+#~
+ #~ def by_slug(self, slug):
+ #~ return StringIO(Book.objects.get(dc_slug=slug
+ #~ ).materialize(publishable=self.publishable
+ #~ ).encode('utf-8'))
def serve_file(file_path, name, mime_type):
yield chunk
chunk = f.read(size)
- response = HttpResponse(mimetype=mime_type)
+ response = HttpResponse(content_type=mime_type)
response['Content-Disposition'] = 'attachment; filename=%s' % name
with open(file_path) as f:
for chunk in read_chunks(f):
+++ /dev/null
-# -*- coding: utf-8 -*-
-from django.contrib.syndication.views import Feed
-from django.shortcuts import get_object_or_404
-from catalogue.models import Book, Chunk
-
-class PublishTrackFeed(Feed):
- title = u"Planowane publikacje"
- link = "/"
-
- def description(self, obj):
- tag, published = obj
- return u"Publikacje, które dotarły co najmniej do etapu: %s" % tag.name
-
- def get_object(self, request, slug):
- published = request.GET.get('published')
- if published is not None:
- published = published == 'true'
- return get_object_or_404(Chunk.tag_model, slug=slug), published
-
- def item_title(self, item):
- return item.title
-
- def items(self, obj):
- tag, published = obj
- books = Book.objects.filter(public=True, _on_track__gte=tag.ordering
- ).order_by('-_on_track', 'title')
- if published is not None:
- books = books.filter(_published=published)
- return books
+++ /dev/null
-[
- {
- "pk": 1,
- "model": "catalogue.chunktag",
- "fields": {
- "ordering": 1,
- "name": "Autokorekta",
- "slug": "first_correction"
- }
- },
- {
- "pk": 2,
- "model": "catalogue.chunktag",
- "fields": {
- "ordering": 2,
- "name": "Tagowanie",
- "slug": "tagging"
- }
- },
- {
- "pk": 3,
- "model": "catalogue.chunktag",
- "fields": {
- "ordering": 3,
- "name": "Korekta",
- "slug": "proofreading"
- }
- },
- {
- "pk": 4,
- "model": "catalogue.chunktag",
- "fields": {
- "ordering": 4,
- "name": "Sprawdzenie przypis\u00f3w \u017ar\u00f3d\u0142a",
- "slug": "annotation-proofreading"
- }
- },
- {
- "pk": 5,
- "model": "catalogue.chunktag",
- "fields": {
- "ordering": 5,
- "name": "Uwsp\u00f3\u0142cze\u015bnienie",
- "slug": "modernisation"
- }
- },
- {
- "pk": 6,
- "model": "catalogue.chunktag",
- "fields": {
- "ordering": 6,
- "name": "Przypisy",
- "slug": "annotations"
- }
- },
- {
- "pk": 7,
- "model": "catalogue.chunktag",
- "fields": {
- "ordering": 7,
- "name": "Motywy",
- "slug": "themes"
- }
- },
- {
- "pk": 8,
- "model": "catalogue.chunktag",
- "fields": {
- "ordering": 8,
- "name": "Ostateczna redakcja literacka",
- "slug": "editor-proofreading"
- }
- },
- {
- "pk": 9,
- "model": "catalogue.chunktag",
- "fields": {
- "ordering": 9,
- "name": "Ostateczna redakcja techniczna",
- "slug": "technical-editor-proofreading"
- }
- }
-]
from django.utils.translation import ugettext_lazy as _
from catalogue.constants import MASTERS
-from catalogue.models import Book, Chunk, Template
+from catalogue.models import Document, Template
-class DocumentCreateForm(forms.ModelForm):
+class DocumentCreateForm(forms.Form):
"""
Form used for creating new documents.
"""
- template = forms.ModelChoiceField(Template.objects, required=False)
-
- class Meta:
- model = Book
- exclude = ['parent', 'parent_number', 'project', 'gallery', 'public']
-
- def __init__(self, *args, **kwargs):
- super(DocumentCreateForm, self).__init__(*args, **kwargs)
- self.fields['slug'].widget.attrs={'class': 'autoslug'}
- self.fields['title'].widget.attrs={'class': 'autoslug-source'}
- self.fields['template'].queryset = Template.objects.filter(is_main=True)
-
- def clean(self):
- super(DocumentCreateForm, self).clean()
- template = self.cleaned_data['template']
- self.cleaned_data['gallery'] = self.cleaned_data['slug']
-
- if template is not None:
- self.cleaned_data['text'] = template.content
-
- if not self.cleaned_data.get("text"):
- self._errors["template"] = self.error_class([_("You must select a template")])
-
- return self.cleaned_data
+ owner_organization = forms.CharField(required=False)
+ title = forms.CharField(required=True)
+ language = forms.CharField(required=True)
+ publisher = forms.CharField(required=False)
+ description = forms.CharField(required=False)
+ rights = forms.CharField(required=False)
+ audience = forms.CharField()
+
+ cover = forms.FileField(required=False)
+
+ #summary = forms.CharField(required=True)
+ #template = forms.ModelChoiceField(Template.objects, required=False)
+
+ #class Meta:
+ #model = Book
+ #exclude = ['parent', 'parent_number', 'project', 'gallery', 'public']
+
+ #def __init__(self, *args, org=None, **kwargs):
+ # super(DocumentCreateForm, self).__init__(*args, **kwargs)
+ #self.fields['slug'].widget.attrs={'class': 'autoslug'}
+ #self.fields['title'].widget.attrs={'class': 'autoslug-source'}
+ #self.fields['template'].queryset = Template.objects.filter(is_main=True)
+
+ #~ def clean(self):
+ #~ super(DocumentCreateForm, self).clean()
+ #template = self.cleaned_data['template']
+ #self.cleaned_data['gallery'] = self.cleaned_data['slug']
+
+ #~ if template is not None:
+ #~ self.cleaned_data['text'] = template.content
+
+ #~ if not self.cleaned_data.get("text"):
+ #~ self._errors["template"] = self.error_class([_("You must select a template")])
+
+ #~ return self.cleaned_data
class DocumentsUploadForm(forms.Form):
return self.cleaned_data
-class ChunkForm(forms.ModelForm):
+class DocumentForm(forms.ModelForm):
"""
Form used for editing a chunk.
"""
user = forms.ModelChoiceField(queryset=
- User.objects.annotate(count=Count('chunk')).
- order_by('last_name', 'first_name'), required=False,
+ User.objects.order_by('last_name', 'first_name'), required=False,
label=_('Assigned to'))
class Meta:
- model = Chunk
- fields = ['title', 'slug', 'gallery_start', 'user', 'stage']
- exclude = ['number']
-
- def __init__(self, *args, **kwargs):
- super(ChunkForm, self).__init__(*args, **kwargs)
- self.fields['gallery_start'].widget.attrs={'class': 'number-input'}
- self.fields['slug'].widget.attrs={'class': 'autoslug'}
- self.fields['title'].widget.attrs={'class': 'autoslug-source'}
-
- def clean_slug(self):
- slug = self.cleaned_data['slug']
- try:
- chunk = Chunk.objects.get(book=self.instance.book, slug=slug)
- except Chunk.DoesNotExist:
- return slug
- if chunk == self.instance:
- return slug
- raise forms.ValidationError(_('Chunk with this slug already exists'))
+ model = Document
+ fields = ['user', 'stage']
-class ChunkAddForm(ChunkForm):
+class DocumentAddForm(DocumentForm):
"""
Form used for adding a chunk to a document.
"""
raise forms.ValidationError(_('Chunk with this slug already exists'))
-class BookAppendForm(forms.Form):
- """
- Form for appending a book to another book.
- It means moving all chunks from book A to book B and deleting A.
- """
- append_to = forms.ModelChoiceField(queryset=Book.objects.all(),
- label=_("Append to"))
-
- def __init__(self, book, *args, **kwargs):
- ret = super(BookAppendForm, self).__init__(*args, **kwargs)
- self.fields['append_to'].queryset = Book.objects.exclude(pk=book.pk)
- return ret
-
-
class BookForm(forms.ModelForm):
"""Form used for editing a Book."""
class Meta:
- model = Book
+ model = Document
exclude = ['project']
def __init__(self, *args, **kwargs):
"""
master = forms.ChoiceField(choices=((m, m) for m in MASTERS))
+
+
+class DocumentForkForm(forms.Form):
+ """
+ Form used for forking documents.
+ """
+ owner_organization = forms.CharField(required=False)
self.src_size = len(files_other)
if files and files_other:
- print "compare %s with %s" % (files[-1], files_other[0])
if filecmp.cmp(
join(self.path(self.dest), files[-1]),
join(self.path(self.src), files_other[0]),
rmtree(join(self.path(self.src)))
return self.dest
+
+
+# Maybe subclass?
+def sstdocument(text):
+ #from catalogue.ebook_utils import RedakcjaDocProvider
+ from librarian.document import Document
+
+ return Document.from_string(
+ text,
+ )
msgstr ""
"Project-Id-Version: Platforma Redakcyjna\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-04-01 11:28+0200\n"
+"POT-Creation-Date: 2016-01-26 12:15+0100\n"
"PO-Revision-Date: 2014-04-01 11:29+0100\n"
"Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
"Language-Team: Fundacja Nowoczesna Polska <fundacja@nowoczesnapolska.org."
"|| n%100>=20) ? 1 : 2);\n"
"X-Generator: Poedit 1.5.4\n"
-#: forms.py:38
-msgid "You must select a template"
-msgstr "Musisz wybrać szablon"
-
-#: forms.py:47
+#: forms.py:59
msgid "ZIP file"
msgstr "Plik ZIP"
-#: forms.py:48
+#: forms.py:60
msgid "Directories are documents in chunks"
msgstr "Katalogi zawierają dokumenty w częściach"
-#: forms.py:72
+#: forms.py:83 templates/catalogue/book_list/book_list.html:11
msgid "Assigned to"
msgstr "Przypisane do"
-#: forms.py:93 forms.py:107
+#: forms.py:101
msgid "Chunk with this slug already exists"
msgstr "Część z tym slugiem już istnieje"
-#: forms.py:116
-msgid "Append to"
-msgstr "Dołącz do"
-
-#: views.py:161
-#, python-format
-msgid "Slug already used for %s"
-msgstr "Slug taki sam jak dla pliku %s"
-
-#: views.py:163
-msgid "Slug already used in repository."
-msgstr "Dokument o tym slugu już istnieje w repozytorium."
-
-#: views.py:169
-msgid "File should be UTF-8 encoded."
-msgstr "Plik powinien mieć kodowanie UTF-8."
-
-#: models/book.py:28 models/chunk.py:23
+#: models/book.py:30
msgid "title"
msgstr "tytuł"
-#: models/book.py:29 models/chunk.py:24
+#: models/book.py:31
msgid "slug"
msgstr "slug"
-#: models/book.py:30
+#: models/book.py:32
msgid "public"
msgstr "publiczna"
-#: models/book.py:35
+#: models/book.py:40
msgid "parent"
msgstr "rodzic"
-#: models/book.py:36
+#: models/book.py:41
msgid "parent number"
msgstr "numeracja rodzica"
-#: models/book.py:55 models/chunk.py:21 models/publish_log.py:17
-msgid "book"
-msgstr "książka"
-
-#: models/book.py:56
-msgid "books"
-msgstr "książki"
-
-#: models/book.py:255
+#: models/book.py:260
msgid "No chunks in the book."
msgstr "Książka nie ma części."
-#: models/book.py:259
+#: models/book.py:264
msgid "Not all chunks have publishable revisions."
msgstr "Niektóre części nie są gotowe do publikacji."
-#: models/book.py:266
+#: models/book.py:271
msgid "Invalid XML"
msgstr "Nieprawidłowy XML"
-#: models/book.py:268
+#: models/book.py:273
msgid "No Dublin Core found."
msgstr "Brak sekcji Dublin Core."
-#: models/book.py:270
+#: models/book.py:275
msgid "Invalid Dublin Core"
msgstr "Nieprawidłowy Dublin Core"
-#: models/book.py:273
+#: models/book.py:278
msgid "rdf:about is not"
msgstr "rdf:about jest różny od"
-#: models/chunk.py:22
-msgid "number"
-msgstr "numer"
-
-#: models/chunk.py:25
-msgid "gallery start"
-msgstr "początek galerii"
-
-#: models/chunk.py:40
-msgid "chunk"
-msgstr "część"
+#: models/document.py:23
+msgid "stage"
+msgstr "etap"
-#: models/chunk.py:41
-msgid "chunks"
-msgstr "części"
+#: models/document.py:31 models/publish_log.py:15
+msgid "document"
+msgstr "dokument"
-#: models/project.py:13
-msgid "name"
-msgstr "nazwa"
+#: models/document.py:32
+msgid "documents"
+msgstr "dokumenty"
-#: models/project.py:14
-msgid "notes"
-msgstr "notatki"
+#: models/publish_log.py:16
+msgid "revision"
+msgstr "rewizja"
-#: models/publish_log.py:18
+#: models/publish_log.py:17
msgid "time"
msgstr "czas"
-#: models/publish_log.py:19 templates/catalogue/wall.html:18
+#: models/publish_log.py:18 templates/catalogue/wall.html:18
msgid "user"
msgstr "użytkownik"
-#: models/publish_log.py:24 models/publish_log.py:33
+#: models/publish_log.py:22
msgid "book publish record"
msgstr "zapis publikacji książki"
-#: models/publish_log.py:25
+#: models/publish_log.py:23
msgid "book publish records"
msgstr "zapisy publikacji książek"
-#: models/publish_log.py:34
-msgid "change"
-msgstr "zmiana"
-
-#: models/publish_log.py:38
-msgid "chunk publish record"
-msgstr "zapis publikacji części"
-
-#: models/publish_log.py:39
-msgid "chunk publish records"
-msgstr "zapisy publikacji części"
-
-#: templates/catalogue/activity.html:9 templatetags/catalogue.py:29
+#: templates/catalogue/activity.html:9
msgid "Activity"
msgstr "Aktywność"
-#: templates/catalogue/base.html:8
-msgid "Platforma Redakcyjna"
-msgstr "Platforma Redakcyjna"
-
#: templates/catalogue/book_append_to.html:9
msgid "Append book"
msgstr "Dołącz książkę"
-#: templates/catalogue/book_detail.html:14
+#: templates/catalogue/book_delete.html:5
+#: templates/catalogue/book_owner.html:5
+#: templates/catalogue/book_schedule.html:10
+#: templates/catalogue/my_page.html:12
+msgid "My resources"
+msgstr "Moje zasoby"
+
+#: templates/catalogue/book_delete.html:13
+msgid "Really delete the resource?"
+msgstr "Naprawdę usunąć zasób"
+
+#: templates/catalogue/book_delete.html:17
+#: templates/catalogue/book_list/book.html:23
+#: templates/catalogue/book_text.html:49
+msgid "Delete"
+msgstr "Usun"
+
+#: templates/catalogue/book_delete.html:18
+msgid "Cancel"
+msgstr "Anuluj"
+
+#: templates/catalogue/book_detail.html:13
+#: templates/catalogue/book_list/book.html:22
+msgid "Schedule"
+msgstr "Plan"
+
#: templates/catalogue/book_edit.html:9 templates/catalogue/chunk_edit.html:12
msgid "Save"
msgstr "Zapisz"
-#: templates/catalogue/book_detail.html:21
-msgid "Edit gallery"
-msgstr "Edytuj galerię"
+#: templates/catalogue/book_list/book.html:12
+msgid "change"
+msgstr "zmiana"
+
+#: templates/catalogue/book_list/book.html:21
+#: templates/catalogue/book_schedule.html:7 templatetags/wall.py:51
+msgid "Edit"
+msgstr "Zmień"
+
+#: templates/catalogue/book_list/book_list.html:8
+#: templates/catalogue/document_create_missing.html:24
+msgid "Title"
+msgstr "Tytuł"
+
+#: templates/catalogue/book_list/book_list.html:9
+#: templates/catalogue/book_owner.html:16
+#: templates/catalogue/document_create_missing.html:11
+#: templates/catalogue/finished.html:26 templates/catalogue/upcoming.html:26
+msgid "Owner"
+msgstr "Właściciel"
+
+#: templates/catalogue/book_list/book_list.html:10
+#: templates/catalogue/book_schedule.html:28
+msgid "Stage"
+msgstr "Etap"
+
+#: templates/catalogue/book_list/book_list.html:12
+#: templates/catalogue/book_schedule.html:30
+msgid "Deadline"
+msgstr "Termin"
+
+#: templates/catalogue/book_list/book_list.html:25
+msgid "No resources found."
+msgstr "Nie znaleziono zasobów."
-#: templates/catalogue/book_detail.html:24
-msgid "Append to other book"
-msgstr "Dołącz do innej książki"
+#: templates/catalogue/book_list/chunk.html:6
+#: templates/catalogue/chunk_edit.html:5
+msgid "Chunk settings"
+msgstr "Ustawienia części"
-#: templates/catalogue/book_detail.html:30
-msgid "Chunks"
-msgstr "Części"
+#: templates/catalogue/book_owner.html:26
+#: templates/catalogue/book_text.html:48
+msgid "Change owner"
+msgstr "Zmień właściciela"
-#: templates/catalogue/book_detail.html:45 templatetags/wall.py:78
-msgid "Publication"
-msgstr "Publikacja"
+#: templates/catalogue/book_schedule.html:8
+#: templates/catalogue/book_text.html:47 templates/catalogue/book_text.html:55
+#: templates/catalogue/document_fork.html:20
+msgid "Create another version"
+msgstr "Utwórz odrębną wersję"
+
+#: templates/catalogue/book_schedule.html:29
+msgid "Person"
+msgstr "Osoba"
+
+#: templates/catalogue/book_schedule.html:57
+msgid "Current stage"
+msgstr "Aktualny etap"
+
+#: templates/catalogue/book_schedule.html:72
+msgid "Update"
+msgstr "Zaktualizuj"
+
+#: templates/catalogue/book_text.html:23 templates/catalogue/book_text.html:30
+msgid "Resource owner"
+msgstr "Właściciel zasobu"
+
+#: templates/catalogue/book_text.html:38
+msgid "You can edit your resource here."
+msgstr "Tutaj możesz edytować zasób"
-#: templates/catalogue/book_detail.html:54
-msgid "Last published"
-msgstr "Ostatnio opublikowano"
+#: templates/catalogue/book_text.html:42
+msgid "You can assign work stages and deadlines to people on your team."
+msgstr "Możesz przypisywać etapy prac i terminy osobom w Twoim zespole."
-#: templates/catalogue/book_detail.html:64
-msgid "Full XML"
-msgstr "Pełny XML"
+#: templates/catalogue/book_text.html:46
+msgid ""
+"You can also create another, independent version of this resource – e.g. for "
+"translation to other language or simply for adapting it to your needs."
+msgstr ""
+"Możesz też utworzyć osobną, niezależną wersję tego zasobu – np. w celu "
+"tłumaczenia na inny język albo po prostu aby dostosować go do swoich potrzeb."
+
+#: templates/catalogue/book_text.html:54
+msgid ""
+"You can create and edit another, independent version of this resource – e.g. "
+"for translation to other language or simply for adapting it to your needs."
+msgstr ""
+"Możesz utworzyć osobną, niezależną wersję tego zasobu – np. w celu "
+"tłumaczenia na inny język albo po prostu aby dostosować go do swoich potrzeb."
+
+
+#: templates/catalogue/book_text.html:69
+#, python-format
+msgid ""
+"This is a preview of a specific revision of <a href=\"%(url)s\">this "
+"resource</a>."
+msgstr ""
+"To jest podgląd konkretnej rewizji <a href=\"%(url)s\">tego zasobu</a>."
+
+#: templates/catalogue/book_text.html:74
+#, python-format
+msgid ""
+"There have been some changes since this revision. <a href=\"%(url)s\">See "
+"the current revision.</a>"
+msgstr ""
+"W dokumencie są zmiany nowsze od aktualnie oglądanej rewizji. <a href=\"%(url)s\">Zobacz aktualną rewizję.</a>"
+
+#: templates/catalogue/book_text.html:80
+#, python-format
+msgid "This resource has a <a href=\"%(url)s\">published version</a>."
+msgstr "Ten zasób posiada<a href=\"%(url)s\">opublikowaną wersję</a>."
-#: templates/catalogue/book_detail.html:65
-msgid "HTML version"
-msgstr "Wersja HTML"
+#: templates/catalogue/book_text.html:83
+msgid "This resource hasn't been published yet."
+msgstr "Ten zasób nie został jeszcze opublikowany."
-#: templates/catalogue/book_detail.html:66
-msgid "TXT version"
-msgstr "Wersja TXT"
+#: templates/catalogue/book_text.html:89
+msgid "Do you really want to publish this revision?"
+msgstr "Na pewno chcesz opublikować tę rewizję?"
-#: templates/catalogue/book_detail.html:67
-msgid "PDF version"
-msgstr "Wersja PDF"
+#: templates/catalogue/book_text.html:94
+msgid ""
+"When the resource is ready for use, you can mark its specific revision as "
+"published here."
+msgstr ""
+"Kiedy zasób jest gotowy do użytku, możesz oznaczyć go jako opublikowany."
-#: templates/catalogue/book_detail.html:68
-msgid "EPUB version"
-msgstr "Wersja EPUB"
+#: templates/catalogue/book_text.html:95
+msgid "Publish this revision"
+msgstr "Opublikuj tę rewizję"
-#: templates/catalogue/book_detail.html:81
-msgid "Publish"
-msgstr "Opublikuj"
+#: templates/catalogue/book_text.html:99
+msgid "Do you really want to undo publishing this resource?"
+msgstr "Na pewno chcesz wycofać publikację tego zasobu?"
-#: templates/catalogue/book_detail.html:85
-msgid "Log in to publish."
-msgstr "Zaloguj się, aby opublikować."
+#: templates/catalogue/book_text.html:103
+msgid ""
+"If you have published the resource by mistake, you can un-publish it here."
+msgstr ""
+"Jeśli zasób został opublikowany przez pomyłkę, możesz to cofnąć tutaj."
-#: templates/catalogue/book_detail.html:88
-msgid "This book can't be published yet, because:"
-msgstr "Ta książka nie może jeszcze zostać opublikowana. Powód:"
+#: templates/catalogue/book_text.html:104
+msgid "Undo publishing this resource"
+msgstr "Wycofaj publikację tego zasobu"
-#: templates/catalogue/book_detail.html:98
-msgid "Comments"
-msgstr "Komentarze"
+#: templates/catalogue/book_text.html:152
+#: templates/catalogue/document_create_missing.html:57
+#: templates/catalogue/finished.html:24 templates/catalogue/upcoming.html:24
+msgid "Audience"
+msgstr "Odbiorcy"
-#: templates/catalogue/book_text.html:7
-msgid "Redakcja"
+#: templates/catalogue/book_text.html:157
+msgid ""
+"You can download and share a PDF version – and more formats in the future."
msgstr ""
+"Możesz pobrać i opublikować wersję PDF – a w przyszłości również inne formaty."
-#: templates/catalogue/book_text.html:15
-msgid "Table of contents"
-msgstr "Spis treści"
+#: templates/catalogue/book_text.html:159
+msgid "Download PDF"
+msgstr "Pobierz PDF"
-#: templates/catalogue/book_text.html:17
-msgid "Edit. note"
-msgstr "Nota red."
+#: templates/catalogue/book_text.html:166
+msgid ""
+"And, of course, you can read and share the resource in a handy webpage form "
+"right here."
+msgstr ""
+"I, oczywiście, możesz czytać zasób i dzielić się nim w postaci strony internetowej, właśnie tutaj."
#: templates/catalogue/chunk_add.html:5 templates/catalogue/chunk_edit.html:18
msgid "Split chunk"
msgid "Add chunk"
msgstr "Dodaj część"
-#: templates/catalogue/chunk_edit.html:5
-#: templates/catalogue/book_list/chunk.html:6
-msgid "Chunk settings"
-msgstr "Ustawienia części"
-
#: templates/catalogue/chunk_edit.html:10
msgid "Book"
msgstr "Książka"
+#: templates/catalogue/document_create_missing.html:5
+msgid "Create a new resource"
+msgstr "Utwórz nowy zasób"
+
+#: templates/catalogue/document_create_missing.html:15
+msgid ""
+"You can choose whether the resource should be owned by you or by your "
+"organization. This can be changed later."
+msgstr ""
+"Możesz wybrać, czy właścicielem zasobu masz być Ty, czy Twoja organizacja. "
+"Można to zmienić później."
+
+#: templates/catalogue/document_create_missing.html:27
+msgid "Cover image"
+msgstr "Obraz na okładkę"
+
+#: templates/catalogue/document_create_missing.html:30
+msgid "Language"
+msgstr "Język"
+
+#: templates/catalogue/document_create_missing.html:33
+msgid "Publisher"
+msgstr "Wydawca"
+
+#: templates/catalogue/document_create_missing.html:36
+msgid "Rights"
+msgstr "Prawa autorskie"
+
+#: templates/catalogue/document_create_missing.html:40
+msgid ""
+"You should choose a free license for your resource. We recommend using "
+"Creative Commons Attribution - Share Alike."
+msgstr ""
+"Wybierz wolną licencję dla swojego zasobu. Rekomendujemy "
+"Creative Commons Uznanie autorstwa - Na tych samych warunkach."
+
+#: templates/catalogue/document_create_missing.html:44
+msgid "Only set for resources that are not restricted with copyright."
+msgstr "Tylko dla zasobów nie objętych majątkowym prawem autorskim"
+
+#: templates/catalogue/document_create_missing.html:45
+msgid "public domain"
+msgstr "domena publiczna"
+
+#: templates/catalogue/document_create_missing.html:47
+msgid ""
+"Non-copyleft free culture license. See <a target='_blank' href='//"
+"creativecommons.org/choose/'>creativecommons.org</a>"
+msgstr ""
+"Nie-copyleftowa licencja wolnej kultury. Zob. <a target='_blank' href='//"
+"creativecommons.org/choose/'>creativecommons.org</a>"
+
+#: templates/catalogue/document_create_missing.html:48
+msgid "Creative Commons Attribution"
+msgstr "Creative Commons Uznanie autorstwa"
+
+#: templates/catalogue/document_create_missing.html:50
+msgid ""
+"Copyleft free culture license. See <a target='_blank' href='//"
+"creativecommons.org/choose/'>creativecommons.org</a>"
+msgstr ""
+"Copyleftowa licencja wolnej kultury. Zob. <a target='_blank' href='//"
+"creativecommons.org/choose/'>creativecommons.org</a>"
+
+#: templates/catalogue/document_create_missing.html:51
+msgid "Creative Commons Attribution – Share Alike"
+msgstr "Creative Commons Uznanie autorstwa – Na tych samych warunkach"
+
+#: templates/catalogue/document_create_missing.html:53
+msgid ""
+"Copyleft free culture license. See <a target='_blank' href='http://artlibre."
+"org/'>artlibre.org</a>"
+msgstr ""
+"Copyleftowa licencja wolnej kultury. Zob. <a target='_blank' href='http://artlibre."
+"org/'>artlibre.org</a>"
+
+
+#: templates/catalogue/document_create_missing.html:54
+msgid "Free Art License"
+msgstr "Licencja Wolnej Sztuki (FAL)"
+
+#: templates/catalogue/document_create_missing.html:61
+msgid "Choose primary audience for your resource."
+msgstr "Wybierz główną grupę odbiorców Twojego zasobu"
+
+#: templates/catalogue/document_create_missing.html:71
+msgid "Summary"
+msgstr "Podsumowanie"
+
+#: templates/catalogue/document_create_missing.html:75
+msgid "You can provide a short description of the document here."
+msgstr "Możesz tu dodać krótki opis dokumentu."
+
+#: templates/catalogue/document_create_missing.html:79
+msgid "Create resource"
+msgstr "Utwórz zasób"
+
+#: templates/catalogue/document_fork.html:5
+msgid "Create another version of a resource"
+msgstr "Utwórz odrębną wersję zasobu"
+
+#: templates/catalogue/document_fork.html:11
+msgid "Create as"
+msgstr "Utwórz jako"
+
#: templates/catalogue/document_upload.html:8
msgid "Bulk documents upload"
msgstr "Hurtowe dodawanie dokumentów"
msgid "Files skipped due to no <code>.xml</code> extension"
msgstr "Pliki pominięte z powodu braku rozszerzenia <code>.xml</code>."
-#: templates/catalogue/my_page.html:21
-msgid "Your last edited documents"
-msgstr "Twoje ostatnie edycje"
+#: templates/catalogue/finished.html:8
+msgid "Finished resources"
+msgstr "Ukończone zasoby"
-#: templates/catalogue/my_page.html:30 templates/catalogue/user_page.html:13
-msgid "Recent activity for"
-msgstr "Ostatnia aktywność dla:"
+#: templates/catalogue/my_page.html:10
+msgid "New resource +"
+msgstr "Nowy zasób +"
+
+#: templates/catalogue/my_page.html:15
+msgid "Projects"
+msgstr "Projekty"
-#: templates/catalogue/user_list.html:7 templatetags/catalogue.py:31
+#: templates/catalogue/upcoming.html:8
+msgid "Upcoming resources"
+msgstr "Nadchodzące zasoby"
+
+#: templates/catalogue/user_list.html:7
msgid "Users"
msgstr "Użytkownicy"
+#: templates/catalogue/user_page.html:13
+msgid "Recent activity for"
+msgstr "Ostatnia aktywność dla:"
+
#: templates/catalogue/wall.html:28
msgid "not logged in"
msgstr "nie zalogowany"
msgid "No activity recorded."
msgstr "Nie zanotowano aktywności."
-#: templates/catalogue/book_list/book_list.html:27
-msgid "stage"
-msgstr "etap"
-
-#: templates/catalogue/book_list/book_list.html:29
-#: templates/catalogue/book_list/book_list.html:40
-#: templates/catalogue/book_list/book_list.html:51
-msgid "none"
-msgstr "brak"
-
-#: templates/catalogue/book_list/book_list.html:38
-msgid "editor"
-msgstr "redaktor"
-
-#: templates/catalogue/book_list/book_list.html:76
-#, python-format
-msgid "%(c)s module"
-msgid_plural "%(c)s modules"
-msgstr[0] "%(c)s moduł"
-msgstr[1] "%(c)s moduły"
-msgstr[2] "%(c)s modułów"
-
-#: templates/catalogue/book_list/book_list.html:87
-msgid "Set stage"
-msgstr "Ustaw etap"
-
-#: templates/catalogue/book_list/book_list.html:88
-msgid "Set user"
-msgstr "Przypisz redaktora"
-
-#: templates/catalogue/book_list/book_list.html:91
-msgid "Mark publishable"
-msgstr "Oznacz do publikacji"
-
-#: templates/catalogue/book_list/book_list.html:92
-msgid "Mark not publishable"
-msgstr "Odznacz do publikacji"
-
-#: templates/catalogue/book_list/book_list.html:93
-msgid "Other user"
-msgstr "Inny użytkownik"
-
-#: templatetags/book_list.py:84
+#: templatetags/document_list.py:85
msgid "publishable"
msgstr "do publikacji"
-#: templatetags/book_list.py:85
+#: templatetags/document_list.py:86
msgid "changed"
msgstr "zmienione"
-#: templatetags/book_list.py:86
+#: templatetags/document_list.py:87
msgid "published"
msgstr "opublikowane"
-#: templatetags/book_list.py:87
+#: templatetags/document_list.py:88
msgid "unpublished"
msgstr "nie opublikowane"
-#: templatetags/book_list.py:88
+#: templatetags/document_list.py:89
msgid "empty"
msgstr "puste"
-#: templatetags/catalogue.py:27
-msgid "My page"
-msgstr "Moja strona"
-
-#: templatetags/catalogue.py:30
-msgid "All"
-msgstr "Wszystkie"
-
-#: templatetags/catalogue.py:34
-msgid "Add"
-msgstr "Dodaj"
-
#: templatetags/wall.py:49
msgid "Related edit"
msgstr "Powiązana zmiana"
-#: templatetags/wall.py:51
-msgid "Edit"
-msgstr "Zmiana"
+#: templatetags/wall.py:78
+msgid "Publication"
+msgstr "Publikacja"
#: templatetags/wall.py:99
msgid "Comment"
msgstr "Komentarz"
+#: views.py:206
+#, python-format
+msgid "Slug already used for %s"
+msgstr "Slug taki sam jak dla pliku %s"
+
+#: views.py:208
+msgid "Slug already used in repository."
+msgstr "Dokument o tym slugu już istnieje w repozytorium."
+
+#: views.py:214
+msgid "File should be UTF-8 encoded."
+msgstr "Plik powinien mieć kodowanie UTF-8."
+
+#~ msgid "You must select a template"
+#~ msgstr "Musisz wybrać szablon"
+
+#~ msgid "Append to"
+#~ msgstr "Dołącz do"
+
+#~ msgid "book"
+#~ msgstr "książka"
+
+#~ msgid "books"
+#~ msgstr "książki"
+
+#~ msgid "number"
+#~ msgstr "numer"
+
+#~ msgid "gallery start"
+#~ msgstr "początek galerii"
+
+#~ msgid "chunk"
+#~ msgstr "część"
+
+#~ msgid "chunks"
+#~ msgstr "części"
+
+#~ msgid "name"
+#~ msgstr "nazwa"
+
+#~ msgid "notes"
+#~ msgstr "notatki"
+
+#~ msgid "chunk publish record"
+#~ msgstr "zapis publikacji części"
+
+#~ msgid "chunk publish records"
+#~ msgstr "zapisy publikacji części"
+
+#~ msgid "Platforma Redakcyjna"
+#~ msgstr "Platforma Redakcyjna"
+
+#~ msgid "Edit gallery"
+#~ msgstr "Edytuj galerię"
+
+#~ msgid "Append to other book"
+#~ msgstr "Dołącz do innej książki"
+
+#~ msgid "Chunks"
+#~ msgstr "Części"
+
+#~ msgid "Last published"
+#~ msgstr "Ostatnio opublikowano"
+
+#~ msgid "Full XML"
+#~ msgstr "Pełny XML"
+
+#~ msgid "HTML version"
+#~ msgstr "Wersja HTML"
+
+#~ msgid "TXT version"
+#~ msgstr "Wersja TXT"
+
+#~ msgid "PDF version"
+#~ msgstr "Wersja PDF"
+
+#~ msgid "EPUB version"
+#~ msgstr "Wersja EPUB"
+
+#~ msgid "Log in to publish."
+#~ msgstr "Zaloguj się, aby opublikować."
+
+#~ msgid "Table of contents"
+#~ msgstr "Spis treści"
+
+#~ msgid "Edit. note"
+#~ msgstr "Nota red."
+
+#~ msgid "Your last edited documents"
+#~ msgstr "Twoje ostatnie edycje"
+
+#~ msgid "none"
+#~ msgstr "brak"
+
+#~ msgid "editor"
+#~ msgstr "redaktor"
+
+#~ msgid "%(c)s module"
+#~ msgid_plural "%(c)s modules"
+#~ msgstr[0] "%(c)s moduł"
+#~ msgstr[1] "%(c)s moduły"
+#~ msgstr[2] "%(c)s modułów"
+
+#~ msgid "Set user"
+#~ msgstr "Przypisz redaktora"
+
+#~ msgid "Mark publishable"
+#~ msgstr "Oznacz do publikacji"
+
+#~ msgid "Mark not publishable"
+#~ msgstr "Odznacz do publikacji"
+
+#~ msgid "Other user"
+#~ msgstr "Inny użytkownik"
+
+#~ msgid "My page"
+#~ msgstr "Moja strona"
+
+#~ msgid "All"
+#~ msgstr "Wszystkie"
+
+#~ msgid "Add"
+#~ msgstr "Dodaj"
+
#~ msgid "Text file must be UTF-8 encoded."
#~ msgstr "Plik powinien mieć kodowanie UTF-8."
#~ msgid "projects"
#~ msgstr "projekty"
-#~ msgid "Create a new book"
-#~ msgstr "Utwórz nową książkę"
-
-#~ msgid "Create book"
-#~ msgstr "Utwórz książkę"
-
#~ msgid "Book settings"
#~ msgstr "Ustawienia książki"
#~ msgid "status"
#~ msgstr "status"
-#~ msgid "No books found."
-#~ msgstr "Nie znaleziono książek."
-
-#~ msgid "Project"
-#~ msgstr "Projekt"
-
#~ msgid "Covers"
#~ msgstr "Okładki"
#~ msgid "Help"
#~ msgstr "Pomoc"
-#~ msgid "Version"
-#~ msgstr "Wersja"
-
#~ msgid "Unknown"
#~ msgstr "nieznana"
#~ msgid "Clear filter"
#~ msgstr "Wyczyść filtr"
-#~ msgid "Cancel"
-#~ msgstr "Anuluj"
-
#~ msgid "Revert"
#~ msgstr "Przywróć"
#~ msgid "Search and replace"
#~ msgstr "Znajdź i zamień"
-#~ msgid "Source code"
-#~ msgstr "Kod źródłowy"
-
-#~ msgid "Title"
-#~ msgstr "Tytuł"
-
-#~ msgid "Document ID"
-#~ msgstr "ID dokumentu"
-
-#~ msgid "Current version"
-#~ msgstr "Aktualna wersja"
-
#~ msgid "Last edited by"
#~ msgstr "Ostatnio edytowane przez"
-#~ msgid "Summary"
-#~ msgstr "Podsumowanie"
-
#~ msgid "Insert theme"
#~ msgstr "Wstaw motyw"
#~ msgid "Technical Editor's Proofreading"
#~ msgstr "Ostateczna redakcja techniczna"
-#~ msgid "Finished stage: %s"
-#~ msgstr "Ukończony etap: %s"
-
#~ msgid "Refresh"
#~ msgstr "Odśwież"
+++ /dev/null
-# -*- coding: utf-8 -*-
-
-import csv
-from optparse import make_option
-import re
-import sys
-import urllib
-import urllib2
-
-from django.contrib.auth.models import User
-from django.core.management.base import BaseCommand
-from django.core.management.color import color_style
-from django.db import transaction
-
-from slughifi import slughifi
-from catalogue.models import Chunk
-
-
-REDMINE_CSV = 'http://redmine.nowoczesnapolska.org.pl/projects/wl-publikacje/issues.csv'
-REDAKCJA_URL = 'http://redakcja.wolnelektury.pl/documents/'
-
-
-class Command(BaseCommand):
- option_list = BaseCommand.option_list + (
- make_option('-r', '--redakcja', dest='redakcja', metavar='URL',
- help='Base URL of Redakcja documents',
- default=REDAKCJA_URL),
- make_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
- help='Less output'),
- make_option('-f', '--force', action='store_true', dest='force', default=False,
- help='Force assignment overwrite'),
- )
- help = 'Imports ticket assignments from Redmine.'
- args = '[redmine-csv-url]'
-
- def handle(self, *redmine_csv, **options):
-
- self.style = color_style()
-
- redakcja = options.get('redakcja')
- verbose = options.get('verbose')
- force = options.get('force')
-
- if not redmine_csv:
- if verbose:
- print "Using default Redmine CSV URL:", REDMINE_CSV
- redmine_csv = REDMINE_CSV
-
- # Start transaction management.
- transaction.commit_unless_managed()
- transaction.enter_transaction_management()
- transaction.managed(True)
-
- redakcja_link = re.compile(re.escape(redakcja) + r'([-_.:?&%/a-zA-Z0-9]*)')
-
- all_tickets = 0
- all_chunks = 0
- done_tickets = 0
- done_chunks = 0
- empty_users = 0
- unknown_users = {}
- unknown_books = []
- forced = []
-
- if verbose:
- print 'Downloading CSV file'
- for r in csv.reader(urllib2.urlopen(redmine_csv)):
- if r[0] == '#':
- continue
- all_tickets += 1
-
- username = r[6]
- if not username:
- if verbose:
- print "Empty user, skipping"
- empty_users += 1
- continue
-
- first_name, last_name = unicode(username, 'utf-8').rsplit(u' ', 1)
- try:
- user = User.objects.get(first_name=first_name, last_name=last_name)
- except User.DoesNotExist:
- print self.style.ERROR('Unknown user: ' + username)
- unknown_users.setdefault(username, 0)
- unknown_users[username] += 1
- continue
-
- ticket_done = False
- for fname in redakcja_link.findall(r[-1]):
- fname = unicode(urllib.unquote(fname), 'utf-8', 'ignore')
- if fname.endswith('.xml'):
- fname = fname[:-4]
- fname = fname.replace(' ', '_')
- fname = slughifi(fname)
-
- chunks = Chunk.objects.filter(book__slug=fname)
- if not chunks:
- print self.style.ERROR('Unknown book: ' + fname)
- unknown_books.append(fname)
- continue
- all_chunks += chunks.count()
-
- for chunk in chunks:
- if chunk.user:
- if chunk.user == user:
- continue
- else:
- forced.append((chunk, chunk.user, user))
- if force:
- print self.style.WARNING(
- '%s assigned to %s, forcing change to %s.' %
- (chunk.pretty_name(), chunk.user, user))
- else:
- print self.style.WARNING(
- '%s assigned to %s not to %s, skipping.' %
- (chunk.pretty_name(), chunk.user, user))
- continue
- chunk.user = user
- chunk.save()
- ticket_done = True
- done_chunks += 1
-
- if ticket_done:
- done_tickets += 1
-
-
- # Print results
- print
- print "Results:"
- print "Assignments imported from %d/%d tickets to %d/%d relevalt chunks." % (
- done_tickets, all_tickets, done_chunks, all_chunks)
- if empty_users:
- print "%d tickets were unassigned." % empty_users
- if forced:
- print "%d assignments conficts (%s):" % (
- len(forced), "changed" if force else "left")
- for chunk, orig, user in forced:
- print " %s: \t%s \t-> %s" % (
- chunk.pretty_name(), orig.username, user.username)
- if unknown_books:
- print "%d unknown books:" % len(unknown_books)
- for fname in unknown_books:
- print " %s" % fname
- if unknown_users:
- print "%d unknown users:" % len(unknown_users)
- for name in unknown_users:
- print " %s (%d tickets)" % (name, unknown_users[name])
- print
-
-
- transaction.commit()
- transaction.leave_transaction_management()
-
+++ /dev/null
-# -*- coding: utf-8 -*-
-
-from optparse import make_option
-import sys
-
-from django.contrib.auth.models import User
-from django.core.management.base import BaseCommand
-from django.core.management.color import color_style
-from django.db import transaction
-
-from slughifi import slughifi
-from catalogue.models import Book
-
-
-def common_prefix(texts):
- common = []
-
- min_len = min(len(text) for text in texts)
- for i in range(min_len):
- chars = list(set([text[i] for text in texts]))
- if len(chars) > 1:
- break
- common.append(chars[0])
- return "".join(common)
-
-
-class Command(BaseCommand):
- option_list = BaseCommand.option_list + (
- make_option('-s', '--slug', dest='new_slug', metavar='SLUG',
- help='New slug of the merged book (defaults to common part of all slugs).'),
- make_option('-t', '--title', dest='new_title', metavar='TITLE',
- help='New title of the merged book (defaults to common part of all titles).'),
- make_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
- help='Less output'),
- make_option('-g', '--guess', action='store_true', dest='guess', default=False,
- help='Try to guess what merges are needed (but do not apply them).'),
- make_option('-d', '--dry-run', action='store_true', dest='dry_run', default=False,
- help='Dry run: do not actually change anything.'),
- make_option('-f', '--force', action='store_true', dest='force', default=False,
- help='On slug conflict, hide the original book to archive.'),
- )
- help = 'Merges multiple books into one.'
- args = '[slug]...'
-
-
- def print_guess(self, dry_run=True, force=False):
- from collections import defaultdict
- from pipes import quote
- import re
-
- def read_slug(slug):
- res = []
- res.append((re.compile(ur'__?(przedmowa)$'), -1))
- res.append((re.compile(ur'__?(cz(esc)?|ksiega|rozdzial)__?(?P<n>\d*)$'), None))
- res.append((re.compile(ur'__?(rozdzialy__?)?(?P<n>\d*)-'), None))
-
- for r, default in res:
- m = r.search(slug)
- if m:
- start = m.start()
- try:
- return int(m.group('n')), slug[:start]
- except IndexError:
- return default, slug[:start]
- return None, slug
-
- def file_to_title(fname):
- """ Returns a title-like version of a filename. """
- parts = (p.replace('_', ' ').title() for p in fname.split('__'))
- return ' / '.join(parts)
-
- merges = defaultdict(list)
- slugs = []
- for b in Book.objects.all():
- slugs.append(b.slug)
- n, ns = read_slug(b.slug)
- if n is not None:
- merges[ns].append((n, b))
-
- conflicting_slugs = []
- for slug in sorted(merges.keys()):
- merge_list = sorted(merges[slug])
- if len(merge_list) < 2:
- continue
-
- merge_slugs = [b.slug for i, b in merge_list]
- if slug in slugs and slug not in merge_slugs:
- conflicting_slugs.append(slug)
-
- title = file_to_title(slug)
- print "./manage.py merge_books %s%s--title=%s --slug=%s \\\n %s\n" % (
- '--dry-run ' if dry_run else '',
- '--force ' if force else '',
- quote(title), slug,
- " \\\n ".join(merge_slugs)
- )
-
- if conflicting_slugs:
- if force:
- print self.style.NOTICE('# These books will be archived:')
- else:
- print self.style.ERROR('# ERROR: Conflicting slugs:')
- for slug in conflicting_slugs:
- print '#', slug
-
-
- def handle(self, *slugs, **options):
-
- self.style = color_style()
-
- force = options.get('force')
- guess = options.get('guess')
- dry_run = options.get('dry_run')
- new_slug = options.get('new_slug').decode('utf-8')
- new_title = options.get('new_title').decode('utf-8')
- verbose = options.get('verbose')
-
- if guess:
- if slugs:
- print "Please specify either slugs, or --guess."
- return
- else:
- self.print_guess(dry_run, force)
- return
- if not slugs:
- print "Please specify some book slugs"
- return
-
- # Start transaction management.
- transaction.commit_unless_managed()
- transaction.enter_transaction_management()
- transaction.managed(True)
-
- books = [Book.objects.get(slug=slug) for slug in slugs]
- common_slug = common_prefix(slugs)
- common_title = common_prefix([b.title for b in books])
-
- if not new_title:
- new_title = common_title
- elif common_title.startswith(new_title):
- common_title = new_title
-
- if not new_slug:
- new_slug = common_slug
- elif common_slug.startswith(new_slug):
- common_slug = new_slug
-
- if slugs[0] != new_slug and Book.objects.filter(slug=new_slug).exists():
- self.style.ERROR('Book already exists, skipping!')
-
-
- if dry_run and verbose:
- print self.style.NOTICE('DRY RUN: nothing will be changed.')
- print
-
- if verbose:
- print "New title:", self.style.NOTICE(new_title)
- print "New slug:", self.style.NOTICE(new_slug)
- print
-
- for i, book in enumerate(books):
- chunk_titles = []
- chunk_slugs = []
-
- book_title = book.title[len(common_title):].replace(' / ', ' ').lstrip()
- book_slug = book.slug[len(common_slug):].replace('__', '_').lstrip('-_')
- for j, chunk in enumerate(book):
- if j:
- new_chunk_title = book_title + '_%d' % j
- new_chunk_slug = book_slug + '_%d' % j
- else:
- new_chunk_title, new_chunk_slug = book_title, book_slug
-
- chunk_titles.append(new_chunk_title)
- chunk_slugs.append(new_chunk_slug)
-
- if verbose:
- print "title: %s // %s -->\n %s // %s\nslug: %s / %s -->\n %s / %s" % (
- book.title, chunk.title,
- new_title, new_chunk_title,
- book.slug, chunk.slug,
- new_slug, new_chunk_slug)
- print
-
- if not dry_run:
- try:
- conflict = Book.objects.get(slug=new_slug)
- except Book.DoesNotExist:
- conflict = None
- else:
- if conflict == books[0]:
- conflict = None
-
- if conflict:
- if force:
- # FIXME: there still may be a conflict
- conflict.slug = '.' + conflict.slug
- conflict.save()
- print self.style.NOTICE('Book with slug "%s" moved to "%s".' % (new_slug, conflict.slug))
- else:
- print self.style.ERROR('ERROR: Book with slug "%s" exists.' % new_slug)
- return
-
- if i:
- books[0].append(books[i], slugs=chunk_slugs, titles=chunk_titles)
- else:
- book.title = new_title
- book.slug = new_slug
- book.save()
- for j, chunk in enumerate(book):
- chunk.title = chunk_titles[j]
- chunk.slug = chunk_slugs[j]
- chunk.save()
-
-
- transaction.commit()
- transaction.leave_transaction_management()
-
+++ /dev/null
-from django.db import models
-
-class VisibleManager(models.Manager):
- def get_query_set(self):
- return super(VisibleManager, self).get_query_set().exclude(_hidden=True)
-# encoding: 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 'Book'
- db.create_table('catalogue_book', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('title', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
- ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=128, db_index=True)),
- ('gallery', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
- ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='children', null=True, to=orm['catalogue.Book'])),
- ('parent_number', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)),
- ('_short_html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
- ('_single', self.gf('django.db.models.fields.NullBooleanField')(db_index=True, null=True, blank=True)),
- ('_new_publishable', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)),
- ('_published', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)),
- ))
- db.send_create_signal('catalogue', ['Book'])
-
- # Adding model 'Chunk'
- db.create_table('catalogue_chunk', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('creator', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='created_documents', null=True, to=orm['auth.User'])),
- ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
- ('book', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Book'])),
- ('number', self.gf('django.db.models.fields.IntegerField')()),
- ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)),
- ('title', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
- ('_short_html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
- ('_hidden', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)),
- ('_changed', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)),
- ('stage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ChunkTag'], null=True, blank=True)),
- ('head', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['catalogue.ChunkChange'], null=True, blank=True)),
- ))
- db.send_create_signal('catalogue', ['Chunk'])
-
- # Adding unique constraint on 'Chunk', fields ['book', 'number']
- db.create_unique('catalogue_chunk', ['book_id', 'number'])
-
- # Adding unique constraint on 'Chunk', fields ['book', 'slug']
- db.create_unique('catalogue_chunk', ['book_id', 'slug'])
-
- # Adding model 'ChunkTag'
- db.create_table('catalogue_chunktag', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=64)),
- ('slug', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=64, unique=True, null=True, blank=True)),
- ('ordering', self.gf('django.db.models.fields.IntegerField')()),
- ))
- db.send_create_signal('catalogue', ['ChunkTag'])
-
- # Adding model 'ChunkChange'
- db.create_table('catalogue_chunkchange', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
- ('author_name', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)),
- ('author_email', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)),
- ('revision', self.gf('django.db.models.fields.IntegerField')(db_index=True)),
- ('parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='children', null=True, blank=True, to=orm['catalogue.ChunkChange'])),
- ('merge_parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='merge_children', null=True, blank=True, to=orm['catalogue.ChunkChange'])),
- ('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
- ('created_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, db_index=True)),
- ('publishable', self.gf('django.db.models.fields.BooleanField')(default=False)),
- ('tree', self.gf('django.db.models.fields.related.ForeignKey')(related_name='change_set', to=orm['catalogue.Chunk'])),
- ('data', self.gf('django.db.models.fields.files.FileField')(max_length=100)),
- ))
- db.send_create_signal('catalogue', ['ChunkChange'])
-
- # Adding unique constraint on 'ChunkChange', fields ['tree', 'revision']
- db.create_unique('catalogue_chunkchange', ['tree_id', 'revision'])
-
- # Adding M2M table for field tags on 'ChunkChange'
- db.create_table('catalogue_chunkchange_tags', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('chunkchange', models.ForeignKey(orm['catalogue.chunkchange'], null=False)),
- ('chunktag', models.ForeignKey(orm['catalogue.chunktag'], null=False))
- ))
- db.create_unique('catalogue_chunkchange_tags', ['chunkchange_id', 'chunktag_id'])
-
- # Adding model 'BookPublishRecord'
- db.create_table('catalogue_bookpublishrecord', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('book', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.Book'])),
- ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
- ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
- ))
- db.send_create_signal('catalogue', ['BookPublishRecord'])
-
- # Adding model 'ChunkPublishRecord'
- db.create_table('catalogue_chunkpublishrecord', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('book_record', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.BookPublishRecord'])),
- ('change', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.ChunkChange'])),
- ))
- db.send_create_signal('catalogue', ['ChunkPublishRecord'])
-
-
- def backwards(self, orm):
-
- # Removing unique constraint on 'ChunkChange', fields ['tree', 'revision']
- db.delete_unique('catalogue_chunkchange', ['tree_id', 'revision'])
-
- # Removing unique constraint on 'Chunk', fields ['book', 'slug']
- db.delete_unique('catalogue_chunk', ['book_id', 'slug'])
-
- # Removing unique constraint on 'Chunk', fields ['book', 'number']
- db.delete_unique('catalogue_chunk', ['book_id', 'number'])
-
- # Deleting model 'Book'
- db.delete_table('catalogue_book')
-
- # Deleting model 'Chunk'
- db.delete_table('catalogue_chunk')
-
- # Deleting model 'ChunkTag'
- db.delete_table('catalogue_chunktag')
-
- # Deleting model 'ChunkChange'
- db.delete_table('catalogue_chunkchange')
-
- # Removing M2M table for field tags on 'ChunkChange'
- db.delete_table('catalogue_chunkchange_tags')
-
- # Deleting model 'BookPublishRecord'
- db.delete_table('catalogue_bookpublishrecord')
-
- # Deleting model 'ChunkPublishRecord'
- db.delete_table('catalogue_chunkpublishrecord')
-
-
- 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'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- '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'})
- },
- 'catalogue.book': {
- 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'},
- '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
- 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
- },
- 'catalogue.bookpublishrecord': {
- 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
- },
- 'catalogue.chunk': {
- 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
- '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_documents'", 'null': 'True', 'to': "orm['auth.User']"}),
- 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'number': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
- 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.chunkchange': {
- 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
- 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
- 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
- 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
- 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
- },
- 'catalogue.chunkpublishrecord': {
- 'Meta': {'object_name': 'ChunkPublishRecord'},
- 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
- },
- 'catalogue.chunktag': {
- 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'ordering': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', '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'})
- }
- }
-
- complete_apps = ['catalogue']
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import django.contrib.auth.models
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('auth', '0006_require_contenttypes_0002'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('dvcs', '__first__'),
+ ('organizations', '__first__'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Document',
+ fields=[
+ ('ref_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='dvcs.Ref')),
+ ('stage', models.CharField(default='', max_length=128, verbose_name='stage', blank=True)),
+ ('owner_organization', models.ForeignKey(to='organizations.Organization', null=True)),
+ ],
+ options={
+ 'verbose_name': 'document',
+ 'verbose_name_plural': 'documents',
+ },
+ bases=('dvcs.ref',),
+ ),
+ migrations.CreateModel(
+ name='Template',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=255)),
+ ('content', models.TextField()),
+ ('is_main', models.BooleanField()),
+ ('is_partial', models.BooleanField()),
+ ],
+ ),
+ migrations.CreateModel(
+ name='User',
+ fields=[
+ ],
+ options={
+ 'proxy': True,
+ },
+ bases=('auth.user',),
+ managers=[
+ (b'objects', django.contrib.auth.models.UserManager()),
+ ],
+ ),
+ migrations.AddField(
+ model_name='document',
+ name='owner_user',
+ field=models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True),
+ ),
+ ]
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('catalogue', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='document',
+ name='assigned_to',
+ field=models.ForeignKey(related_name='assignments', to=settings.AUTH_USER_MODEL, null=True),
+ ),
+ ]
+++ /dev/null
-# encoding: 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):
-
- from django.core.management import call_command
- call_command("loaddata", "stages.json")
-
-
- 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'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- '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'})
- },
- 'catalogue.book': {
- 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'},
- '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
- 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
- },
- 'catalogue.bookpublishrecord': {
- 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
- },
- 'catalogue.chunk': {
- 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
- '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_documents'", 'null': 'True', 'to': "orm['auth.User']"}),
- 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'number': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
- 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.chunkchange': {
- 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
- 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
- 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
- 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
- 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
- },
- 'catalogue.chunkpublishrecord': {
- 'Meta': {'object_name': 'ChunkPublishRecord'},
- 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
- },
- 'catalogue.chunktag': {
- 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'ordering': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', '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'})
- }
- }
-
- complete_apps = ['catalogue']
+++ /dev/null
-# encoding: utf-8
-import datetime
-from zlib import compress
-import os
-import os.path
-import re
-import urllib
-
-from django.db import models
-from south.db import db
-from south.v2 import DataMigration
-
-from django.conf import settings
-from slughifi import slughifi
-
-META_REGEX = re.compile(r'\s*<!--\s(.*?)-->', re.DOTALL | re.MULTILINE)
-STAGE_TAGS_RE = re.compile(r'^#stage-finished: (.*)$', re.MULTILINE)
-AUTHOR_RE = re.compile(r'\s*(.*?)\s*<(.*)>\s*')
-
-
-def urlunquote(url):
- """Unqotes URL
-
- # >>> urlunquote('Za%C5%BC%C3%B3%C5%82%C4%87_g%C4%99%C5%9Bl%C4%85_ja%C5%BA%C5%84')
- # u'Za\u017c\xf3\u0142\u0107_g\u0119\u015bl\u0105 ja\u017a\u0144'
- """
- return unicode(urllib.unquote(url), 'utf-8', 'ignore')
-
-
-def split_name(name):
- parts = name.split('__')
- return parts
-
-
-def file_to_title(fname):
- """ Returns a title-like version of a filename. """
- parts = (p.replace('_', ' ').title() for p in fname.split('__'))
- return ' / '.join(parts)
-
-
-def plain_text(text):
- return re.sub(META_REGEX, '', text, 1)
-
-
-def gallery(slug, text):
- result = {}
-
- m = re.match(META_REGEX, text)
- if m:
- for line in m.group(1).split('\n'):
- try:
- k, v = line.split(':', 1)
- result[k.strip()] = v.strip()
- except ValueError:
- continue
-
- gallery = result.get('gallery', slughifi(slug))
-
- if gallery.startswith('/'):
- gallery = os.path.basename(gallery)
-
- return gallery
-
-
-def migrate_file_from_hg(orm, fname, entry):
- fname = urlunquote(fname)
- print fname
- if fname.endswith('.xml'):
- fname = fname[:-4]
- title = file_to_title(fname)
- fname = slughifi(fname)
-
- # create all the needed objects
- # what if it already exists?
- book = orm.Book.objects.create(
- title=title,
- slug=fname)
- chunk = orm.Chunk.objects.create(
- book=book,
- number=1,
- slug='1')
- try:
- chunk.stage = orm.ChunkTag.objects.order_by('ordering')[0]
- except IndexError:
- chunk.stage = None
-
- maxrev = entry.filerev()
- gallery_link = None
-
- # this will fail if directory exists
- os.makedirs(os.path.join(settings.CATALOGUE_REPO_PATH, str(chunk.pk)))
-
- for rev in xrange(maxrev + 1):
- fctx = entry.filectx(rev)
- data = fctx.data()
- gallery_link = gallery(fname, data)
- data = plain_text(data)
-
- # get tags from description
- description = fctx.description().decode("utf-8", 'replace')
- tags = STAGE_TAGS_RE.findall(description)
- tags = [orm.ChunkTag.objects.get(slug=slug.strip()) for slug in tags]
-
- if tags:
- max_ordering = max(tags, key=lambda x: x.ordering).ordering
- try:
- chunk.stage = orm.ChunkTag.objects.filter(ordering__gt=max_ordering).order_by('ordering')[0]
- except IndexError:
- chunk.stage = None
-
- description = STAGE_TAGS_RE.sub('', description)
-
- author = author_name = author_email = None
- author_desc = fctx.user().decode("utf-8", 'replace')
- m = AUTHOR_RE.match(author_desc)
- if m:
- try:
- author = orm['auth.User'].objects.get(username=m.group(1), email=m.group(2))
- except orm['auth.User'].DoesNotExist:
- author_name = m.group(1)
- author_email = m.group(2)
- else:
- author_name = author_desc
-
- head = orm.ChunkChange.objects.create(
- tree=chunk,
- revision=rev + 1,
- created_at=datetime.datetime.fromtimestamp(fctx.date()[0]),
- description=description,
- author=author,
- author_name=author_name,
- author_email=author_email,
- parent=chunk.head
- )
-
- path = "%d/%d" % (chunk.pk, head.pk)
- abs_path = os.path.join(settings.CATALOGUE_REPO_PATH, path)
- f = open(abs_path, 'wb')
- f.write(compress(data))
- f.close()
- head.data = path
-
- head.tags = tags
- head.save()
-
- chunk.head = head
-
- chunk.save()
- if gallery_link:
- book.gallery = gallery_link
- book.save()
-
-
-class Migration(DataMigration):
-
- def forwards(self, orm):
- try:
- hg_path = settings.WIKI_REPOSITORY_PATH
- except:
- print 'repository not configured, skipping'
- else:
- from mercurial import hg, ui
-
- print 'migrate from', hg_path
- repo = hg.repository(ui.ui(), hg_path)
- tip = repo['tip']
- for fname in tip:
- if fname.startswith('.'):
- continue
- migrate_file_from_hg(orm, fname, tip[fname])
-
-
- def backwards(self, orm):
- "Write your backwards methods here."
- pass
-
-
- 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'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- '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'})
- },
- 'catalogue.book': {
- 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'},
- '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
- 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
- },
- 'catalogue.bookpublishrecord': {
- 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
- },
- 'catalogue.chunk': {
- 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
- '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_documents'", 'null': 'True', 'to': "orm['auth.User']"}),
- 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'number': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
- 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.chunkchange': {
- 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
- 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
- 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
- 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
- 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
- },
- 'catalogue.chunkpublishrecord': {
- 'Meta': {'object_name': 'ChunkPublishRecord'},
- 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
- },
- 'catalogue.chunktag': {
- 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'ordering': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', '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'})
- }
- }
-
- complete_apps = ['catalogue']
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('catalogue', '0002_document_assigned_to'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Plan',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('stage', models.CharField(max_length=128)),
+ ('deadline', models.DateField(null=True, blank=True)),
+ ('document', models.ForeignKey(to='catalogue.Document')),
+ ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True)),
+ ],
+ ),
+ ]
+++ /dev/null
-# encoding: 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):
- "Make sure all revisions start with 1, not 0."
- for zero_commit in orm.ChunkChange.objects.filter(revision=0):
- for change in zero_commit.tree.change_set.all().order_by('-revision'):
- change.revision=models.F('revision') + 1
- change.save()
-
-
- def backwards(self, orm):
- "Write your backwards methods here."
- pass
-
-
- 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'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- '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'})
- },
- 'catalogue.book': {
- 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'},
- '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
- 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
- },
- 'catalogue.bookpublishrecord': {
- 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
- },
- 'catalogue.chunk': {
- 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
- '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}),
- 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'number': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
- 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.chunkchange': {
- 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
- 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
- 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
- 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
- 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
- },
- 'catalogue.chunkpublishrecord': {
- 'Meta': {'object_name': 'ChunkPublishRecord'},
- 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
- },
- 'catalogue.chunktag': {
- 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'ordering': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', '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'})
- }
- }
-
- complete_apps = ['catalogue']
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('dvcs', '0001_initial'),
+ ('catalogue', '0003_plan'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='PublishRecord',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('timestamp', models.DateTimeField(auto_now_add=True, verbose_name='time')),
+ ('document', models.ForeignKey(related_name='publish_log', verbose_name='document', to='catalogue.Document')),
+ ('revision', models.ForeignKey(related_name='publish_log', verbose_name='revision', to='dvcs.Revision')),
+ ('user', models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'ordering': ['-timestamp'],
+ 'verbose_name': 'book publish records',
+ },
+ ),
+ ]
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('catalogue', '0004_publishrecord'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='document',
+ name='deleted',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AlterField(
+ model_name='document',
+ name='stage',
+ field=models.CharField(default=b'Draft', max_length=128, verbose_name='stage', blank=True),
+ ),
+ ]
+++ /dev/null
-# encoding: 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 field 'Chunk.gallery_start'
- db.add_column('catalogue_chunk', 'gallery_start', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True), keep_default=False)
-
-
- def backwards(self, orm):
-
- # Deleting field 'Chunk.gallery_start'
- db.delete_column('catalogue_chunk', 'gallery_start')
-
-
- 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'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- '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'})
- },
- 'catalogue.book': {
- 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'},
- '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
- 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
- },
- 'catalogue.bookpublishrecord': {
- 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
- },
- 'catalogue.chunk': {
- 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
- '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}),
- 'gallery_start': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
- 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'number': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
- 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.chunkchange': {
- 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
- 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
- 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
- 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
- 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
- },
- 'catalogue.chunkpublishrecord': {
- 'Meta': {'object_name': 'ChunkPublishRecord'},
- 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
- },
- 'catalogue.chunktag': {
- 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'ordering': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', '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'})
- }
- }
-
- complete_apps = ['catalogue']
+++ /dev/null
-# encoding: 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 field 'Book.public'
- db.add_column('catalogue_book', 'public', self.gf('django.db.models.fields.BooleanField')(default=True, db_index=True), keep_default=False)
-
-
- def backwards(self, orm):
-
- # Deleting field 'Book.public'
- db.delete_column('catalogue_book', 'public')
-
-
- 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'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- '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'})
- },
- 'catalogue.book': {
- 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'},
- '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
- 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
- },
- 'catalogue.bookpublishrecord': {
- 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
- },
- 'catalogue.chunk': {
- 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
- '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}),
- 'gallery_start': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
- 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'number': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
- 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.chunkchange': {
- 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
- 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
- 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
- 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
- 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
- },
- 'catalogue.chunkpublishrecord': {
- 'Meta': {'object_name': 'ChunkPublishRecord'},
- 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
- },
- 'catalogue.chunktag': {
- 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'ordering': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', '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'})
- }
- }
-
- complete_apps = ['catalogue']
+++ /dev/null
-# encoding: 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 field 'Book.dc_slug'
- db.add_column('catalogue_book', 'dc_slug', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True), keep_default=False)
-
-
- def backwards(self, orm):
-
- # Deleting field 'Book.dc_slug'
- db.delete_column('catalogue_book', 'dc_slug')
-
-
- 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'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- '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'})
- },
- 'catalogue.book': {
- 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'},
- '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'dc_slug': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
- 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
- },
- 'catalogue.bookpublishrecord': {
- 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
- },
- 'catalogue.chunk': {
- 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
- '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}),
- 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}),
- 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'number': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
- 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.chunkchange': {
- 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
- 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
- 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
- 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
- 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
- },
- 'catalogue.chunkpublishrecord': {
- 'Meta': {'object_name': 'ChunkPublishRecord'},
- 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
- },
- 'catalogue.chunktag': {
- 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'ordering': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', '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'})
- }
- }
-
- complete_apps = ['catalogue']
+++ /dev/null
-# encoding: 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 'Book', fields ['dc_slug']
- db.create_index('catalogue_book', ['dc_slug'])
-
-
- def backwards(self, orm):
-
- # Removing index on 'Book', fields ['dc_slug']
- db.delete_index('catalogue_book', ['dc_slug'])
-
-
- 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'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- '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'})
- },
- 'catalogue.book': {
- 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'},
- '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
- 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
- },
- 'catalogue.bookpublishrecord': {
- 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
- },
- 'catalogue.chunk': {
- 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
- '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}),
- 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}),
- 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'number': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
- 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.chunkchange': {
- 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
- 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
- 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
- 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
- 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
- },
- 'catalogue.chunkpublishrecord': {
- 'Meta': {'object_name': 'ChunkPublishRecord'},
- 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
- },
- 'catalogue.chunktag': {
- 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'ordering': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', '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'})
- }
- }
-
- complete_apps = ['catalogue']
+++ /dev/null
-# encoding: 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 field 'Book._on_track'
- db.add_column('catalogue_book', '_on_track', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True), keep_default=False)
-
-
- def backwards(self, orm):
-
- # Deleting field 'Book._on_track'
- db.delete_column('catalogue_book', '_on_track')
-
-
- 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'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- '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'})
- },
- 'catalogue.book': {
- 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'},
- '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
- 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
- },
- 'catalogue.bookpublishrecord': {
- 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
- },
- 'catalogue.chunk': {
- 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
- '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}),
- 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}),
- 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'number': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
- 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.chunkchange': {
- 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
- 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
- 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
- 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
- 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
- },
- 'catalogue.chunkpublishrecord': {
- 'Meta': {'object_name': 'ChunkPublishRecord'},
- 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
- },
- 'catalogue.chunktag': {
- 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'ordering': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', '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'})
- }
- }
-
- complete_apps = ['catalogue']
+++ /dev/null
-# -*- coding: utf-8 -*-
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-
-class Migration(SchemaMigration):
- depends_on = (
- ("cover", "0001_initial"),
- )
-
- def forwards(self, orm):
- # Adding field 'Book.dc_cover_image'
- db.add_column('catalogue_book', 'dc_cover_image',
- self.gf('django.db.models.fields.related.ForeignKey')(to=orm['cover.Image'], null=True, on_delete=models.SET_NULL, blank=True),
- keep_default=False)
-
-
- def backwards(self, orm):
- # Deleting field 'Book.dc_cover_image'
- db.delete_column('catalogue_book', 'dc_cover_image_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'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- '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'})
- },
- 'catalogue.book': {
- 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'},
- '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'dc_cover_image': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cover.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
- 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
- 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
- },
- 'catalogue.bookpublishrecord': {
- 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
- },
- 'catalogue.chunk': {
- 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
- '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': "orm['auth.User']"}),
- 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}),
- 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'number': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
- 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.chunkchange': {
- 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
- 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
- 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
- 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
- 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
- },
- 'catalogue.chunkpublishrecord': {
- 'Meta': {'object_name': 'ChunkPublishRecord'},
- 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
- },
- 'catalogue.chunktag': {
- 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'ordering': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', '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'})
- },
- 'cover.image': {
- 'Meta': {'object_name': 'Image'},
- 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'download_url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'}),
- 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}),
- 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
- }
- }
-
- complete_apps = ['catalogue']
\ No newline at end of file
+++ /dev/null
-# -*- 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 'Project'
- db.create_table(u'catalogue_project', (
- (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
- ('notes', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
- ))
- db.send_create_signal('catalogue', ['Project'])
-
- # Adding field 'Book.project'
- db.add_column(u'catalogue_book', 'project',
- self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Project'], null=True),
- keep_default=False)
-
-
- def backwards(self, orm):
- # Deleting model 'Project'
- db.delete_table(u'catalogue_project')
-
- # Deleting field 'Book.project'
- db.delete_column(u'catalogue_book', 'project_id')
-
-
- models = {
- u'auth.group': {
- 'Meta': {'object_name': 'Group'},
- u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
- },
- u'auth.permission': {
- 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
- 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
- },
- u'auth.user': {
- 'Meta': {'object_name': 'User'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
- 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
- },
- 'catalogue.book': {
- 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'},
- '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'dc_cover_image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cover.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
- 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
- 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Project']", 'null': 'True'}),
- 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
- },
- 'catalogue.bookpublishrecord': {
- 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
- },
- 'catalogue.chunk': {
- 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
- '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': u"orm['auth.User']"}),
- 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}),
- 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'number': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
- 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.chunkchange': {
- 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
- 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
- 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
- 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
- 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
- },
- 'catalogue.chunkpublishrecord': {
- 'Meta': {'object_name': 'ChunkPublishRecord'},
- 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
- },
- 'catalogue.chunktag': {
- 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'ordering': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.project': {
- 'Meta': {'ordering': "['name']", 'object_name': 'Project'},
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
- 'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
- },
- u'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'}),
- u'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'})
- },
- u'cover.image': {
- 'Meta': {'object_name': 'Image'},
- 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'download_url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'}),
- 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}),
- 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
- }
- }
-
- complete_apps = ['catalogue']
\ No newline at end of file
+++ /dev/null
-# -*- coding: utf-8 -*-
-from south.utils import datetime_utils as 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 'Template'
- db.create_table(u'catalogue_template', (
- (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
- ('content', self.gf('django.db.models.fields.TextField')()),
- ))
- db.send_create_signal(u'catalogue', ['Template'])
-
-
- def backwards(self, orm):
- # Deleting model 'Template'
- db.delete_table(u'catalogue_template')
-
-
- models = {
- u'auth.group': {
- 'Meta': {'object_name': 'Group'},
- u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
- },
- u'auth.permission': {
- 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
- 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
- },
- u'auth.user': {
- 'Meta': {'object_name': 'User'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
- 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
- },
- 'catalogue.book': {
- 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'},
- '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'dc_cover_image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cover.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
- 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
- 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Project']", 'null': 'True', 'blank': 'True'}),
- 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
- },
- 'catalogue.bookpublishrecord': {
- 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
- },
- 'catalogue.chunk': {
- 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
- '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': u"orm['auth.User']"}),
- 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}),
- 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'number': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
- 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.chunkchange': {
- 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
- 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
- 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
- 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
- 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
- },
- 'catalogue.chunkpublishrecord': {
- 'Meta': {'object_name': 'ChunkPublishRecord'},
- 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
- },
- 'catalogue.chunktag': {
- 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'ordering': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.project': {
- 'Meta': {'ordering': "['name']", 'object_name': 'Project'},
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
- 'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
- },
- u'catalogue.template': {
- 'Meta': {'object_name': 'Template'},
- 'content': ('django.db.models.fields.TextField', [], {}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
- },
- u'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'}),
- u'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'})
- },
- u'cover.image': {
- 'Meta': {'object_name': 'Image'},
- 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'download_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'unique': 'True', 'null': 'True'}),
- 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}),
- 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
- }
- }
-
- complete_apps = ['catalogue']
\ No newline at end of file
+++ /dev/null
-# -*- coding: utf-8 -*-
-from south.utils import datetime_utils as datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-
-class Migration(SchemaMigration):
-
- def forwards(self, orm):
- # Adding field 'Template.is_main'
- db.add_column(u'catalogue_template', 'is_main',
- self.gf('django.db.models.fields.BooleanField')(default=False),
- keep_default=False)
-
- # Adding field 'Template.is_partial'
- db.add_column(u'catalogue_template', 'is_partial',
- self.gf('django.db.models.fields.BooleanField')(default=False),
- keep_default=False)
-
-
- def backwards(self, orm):
- # Deleting field 'Template.is_main'
- db.delete_column(u'catalogue_template', 'is_main')
-
- # Deleting field 'Template.is_partial'
- db.delete_column(u'catalogue_template', 'is_partial')
-
-
- models = {
- u'auth.group': {
- 'Meta': {'object_name': 'Group'},
- u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
- },
- u'auth.permission': {
- 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
- 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
- },
- u'auth.user': {
- 'Meta': {'object_name': 'User'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
- 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
- },
- 'catalogue.book': {
- 'Meta': {'ordering': "['title', 'slug']", 'object_name': 'Book'},
- '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_on_track': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'dc_cover_image': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['cover.Image']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
- 'dc_slug': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}),
- 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
- 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Project']", 'null': 'True', 'blank': 'True'}),
- 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
- },
- 'catalogue.bookpublishrecord': {
- 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'},
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
- },
- 'catalogue.chunk': {
- 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'},
- '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
- '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
- 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}),
- 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_chunk'", 'null': 'True', 'to': u"orm['auth.User']"}),
- 'gallery_start': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}),
- 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'number': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
- 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.chunkchange': {
- 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'},
- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
- 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
- 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
- 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
- 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}),
- 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}),
- 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"})
- },
- 'catalogue.chunkpublishrecord': {
- 'Meta': {'object_name': 'ChunkPublishRecord'},
- 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}),
- 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
- },
- 'catalogue.chunktag': {
- 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'},
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'ordering': ('django.db.models.fields.IntegerField', [], {}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
- },
- 'catalogue.project': {
- 'Meta': {'ordering': "['name']", 'object_name': 'Project'},
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
- 'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
- },
- 'catalogue.template': {
- 'Meta': {'object_name': 'Template'},
- 'content': ('django.db.models.fields.TextField', [], {}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'is_main': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'is_partial': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
- },
- u'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'}),
- u'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'})
- },
- u'cover.image': {
- 'Meta': {'object_name': 'Image'},
- 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'download_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'unique': 'True', 'null': 'True'}),
- 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}),
- 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
- }
- }
-
- complete_apps = ['catalogue']
\ No newline at end of file
# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
#
from catalogue.models.template import Template
-from catalogue.models.project import Project
-from catalogue.models.chunk import Chunk
-from catalogue.models.publish_log import BookPublishRecord, ChunkPublishRecord
-from catalogue.models.book import Book
-from catalogue.models.listeners import *
+from catalogue.models.document import Document
+from catalogue.models.plan import Plan
+from catalogue.models.publish_log import PublishRecord
+#from catalogue.models.book import Book
+#from catalogue.models.listeners import *
from django.contrib.auth.models import User as AuthUser
# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
#
+from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.db import models, transaction
from django.template.loader import render_to_string
from django.conf import settings
from slughifi import slughifi
-
import apiclient
from catalogue.helpers import cached_in_field, GalleryMerger
from catalogue.models import BookPublishRecord, ChunkPublishRecord, Project
from catalogue.tasks import refresh_instance, book_content_updated
from catalogue.xml_tools import compile_text, split_xml
from cover.models import Image
+from organizations.models import Organization
import os
import shutil
import re
+
class Book(models.Model):
""" A document edited on the wiki """
gallery = models.CharField(u'materiały', max_length=255, blank=True)
project = models.ForeignKey(Project, null=True, blank=True)
+ owner_user = models.ForeignKey(User, null=True)
+ owner_organization = models.ForeignKey(Organization, null=True)
+
#wl_slug = models.CharField(_('title'), max_length=255, null=True, db_index=True, editable=False)
parent = models.ForeignKey('self', null=True, blank=True, verbose_name=_('parent'), related_name="children", editable=False)
parent_number = models.IntegerField(_('parent number'), null=True, blank=True, db_index=True, editable=False)
return len(self) == 1
single = cached_in_field('_single')(is_single)
- @cached_in_field('_short_html')
+ #@cached_in_field('_short_html')
def short_html(self):
return render_to_string('catalogue/book_list/book.html', {'book': self})
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from django.conf import settings
-from django.db import models
-from django.db.utils import IntegrityError
-from django.template.loader import render_to_string
-from django.utils.translation import ugettext_lazy as _
-from catalogue.helpers import cached_in_field
-from catalogue.managers import VisibleManager
-from catalogue.tasks import refresh_instance
-from dvcs import models as dvcs_models
-
-
-class Chunk(dvcs_models.Document):
- """ An editable chunk of text. Every Book text is divided into chunks. """
- REPO_PATH = settings.CATALOGUE_REPO_PATH
-
- book = models.ForeignKey('Book', editable=False, verbose_name=_('book'))
- number = models.IntegerField(_('number'))
- title = models.CharField(_('title'), max_length=255, blank=True)
- slug = models.SlugField(_('slug'))
- gallery_start = models.IntegerField(_('gallery start'), null=True, blank=True, default=1)
-
- # cache
- _short_html = models.TextField(null=True, blank=True, editable=False)
- _hidden = models.NullBooleanField(editable=False)
- _changed = models.NullBooleanField(editable=False)
-
- # managers
- objects = models.Manager()
- visible_objects = VisibleManager()
-
- class Meta:
- app_label = 'catalogue'
- unique_together = [['book', 'number'], ['book', 'slug']]
- ordering = ['number']
- verbose_name = _('chunk')
- verbose_name_plural = _('chunks')
- permissions = [('can_pubmark', 'Can mark for publishing')]
-
- class TagMeta:
- verbose_name = u'etap pracy'
- verbose_name_plural = u'etapy pracy'
-
- # Representing
- # ============
-
- def __unicode__(self):
- return "%d:%d: %s" % (self.book_id, self.number, self.title)
-
- @models.permalink
- def get_absolute_url(self):
- return ("wiki_editor", [self.book.slug, self.slug])
-
- def pretty_name(self, book_length=None):
- title = self.book.title
- if self.title:
- title += ", %s" % self.title
- if book_length > 1:
- title += " (%d/%d)" % (self.number, book_length)
- return title
-
-
- # Creating and manipulation
- # =========================
-
- def split(self, slug, title='', **kwargs):
- """ Create an empty chunk after this one """
- self.book.chunk_set.filter(number__gt=self.number).update(
- number=models.F('number')+1)
- new_chunk = None
- while not new_chunk:
- new_slug = self.book.make_chunk_slug(slug)
- try:
- new_chunk = self.book.chunk_set.create(number=self.number+1,
- slug=new_slug[:50], title=title[:255], **kwargs)
- except IntegrityError:
- pass
- return new_chunk
-
- @classmethod
- def get(cls, book_slug, chunk_slug=None):
- if chunk_slug is None:
- return cls.objects.get(book__slug=book_slug, number=1)
- else:
- return cls.objects.get(book__slug=book_slug, slug=chunk_slug)
-
-
- # State & cache
- # =============
-
- def new_publishable(self):
- change = self.publishable()
- if not change:
- return False
- return change.publish_log.exists()
-
- def is_changed(self):
- if self.head is None:
- return False
- return not self.head.publishable
- changed = cached_in_field('_changed')(is_changed)
-
- def is_hidden(self):
- return self.book.hidden()
- hidden = cached_in_field('_hidden')(is_hidden)
-
- @cached_in_field('_short_html')
- def short_html(self):
- return render_to_string(
- 'catalogue/book_list/chunk.html', {'chunk': self})
-
- def touch(self):
- update = {
- "_changed": self.is_changed(),
- "_hidden": self.is_hidden(),
- "_short_html": None,
- }
- Chunk.objects.filter(pk=self.pk).update(**update)
- refresh_instance(self)
-
- def refresh(self):
- """This should be done offline."""
- self.changed
- self.hidden
- self.short_html
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# This file is part of MIL/PEER, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from __future__ import unicode_literals
+
+from datetime import date
+from django.conf import settings
+from django.db import models
+from django.template.loader import render_to_string
+from django.utils.translation import ugettext_lazy as _
+from dvcs.models import Ref
+from organizations.models import Organization
+from catalogue.constants import STAGES
+
+
+class Document(Ref):
+ """ An editable chunk of text."""
+
+ owner_user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
+ owner_organization = models.ForeignKey(Organization, null=True)
+ stage = models.CharField(_('stage'), max_length=128, blank=True, default=STAGES[0])
+ assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, related_name='assignments')
+ deleted = models.BooleanField(default=False)
+
+ # Where to cache searchable stuff from metadata?
+ # Probably in some kind of search index.
+
+ class Meta:
+ verbose_name = _('document')
+ verbose_name_plural = _('documents')
+
+ def short_html(self):
+ return render_to_string('catalogue/book_list/book.html', {'book': self})
+
+ def meta(self):
+ from lxml import etree
+ # Wrong! should be metadata
+ d = {}
+
+ data = self.materialize()
+ data = data.replace(u'\ufeff', '')
+ # This is bad. The editor shouldn't spew unknown HTML entities.
+ data = data.replace(u' ', u'\u00a0')
+
+ try:
+ t = etree.fromstring(data)
+ except:
+ return {'title': '<<Resource invalid>>'}
+ header = t.find('.//header')
+ if header is None:
+ header = etree.fromstring(data).find('.//{http://nowoczesnapolska.org.pl/sst#}header')
+ d['title'] = getattr(header, 'text', ' ') or ' '
+ #print 'meta', d['title']
+
+ m = t.find('metadata')
+ if m is None:
+ m = t.find('{http://nowoczesnapolska.org.pl/sst#}metadata')
+ if m is not None:
+ c = m.find('{http://purl.org/dc/elements/1.1/}relation.coverimage.url')
+ if c is not None:
+ d['cover_url'] = c.text
+ c = m.find('{http://purl.org/dc/elements/1.1/}audience')
+ if c is not None:
+ d['audience'] = c.text
+
+ return d
+
+ def can_edit(self, user):
+ if self.owner_user:
+ return self.owner_user == user
+ else:
+ return self.owner_organization.is_member(user)
+
+ def set_stage(self, stage):
+ self.stage = stage
+ plan = self.get_plan()
+ if plan is not None:
+ self.assigned_to = plan.user
+ else:
+ self.assigned_to = None
+ self.save()
+
+ def get_plan(self):
+ try:
+ plan = self.plan_set.get(stage=self.stage)
+ except:
+ return None
+ return plan
+
+ def is_overdue(self):
+ plan = self.get_plan()
+ return plan is not None and plan.deadline and plan.deadline < date.today()
from django.db import models
from catalogue.models import Book, Chunk
from catalogue.signals import post_publish
-from dvcs.signals import post_publishable
def book_changed(sender, instance, created, **kwargs):
post_publish.connect(publish_listener)
-def publishable_listener(sender, *args, **kwargs):
- sender.tree.touch()
- sender.tree.book.touch()
-post_publishable.connect(publishable_listener)
-
-
def listener_create(sender, instance, created, **kwargs):
if created:
instance.chunk_set.create(number=1, slug='1')
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# This file is part of MIL/PEER, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django.conf import settings
+from django.db import models
+from catalogue.models import Document
+
+class Plan(models.Model):
+ document = models.ForeignKey(Document)
+ stage = models.CharField(max_length=128)
+ user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
+ deadline = models.DateField(null=True, blank=True)
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from django.db import models
-from django.utils.translation import ugettext_lazy as _
-
-
-class Project(models.Model):
- """ A project, tracked for funding purposes. """
-
- name = models.CharField(_('name'), max_length=255, unique=True)
- notes = models.TextField(_('notes'), blank=True, null=True)
-
- class Meta:
- app_label = 'catalogue'
- ordering = ['name']
- verbose_name = u'poziom edukacyjny'
- verbose_name_plural = u'poziomy edukacyjne'
-
- def __unicode__(self):
- return self.name
# -*- coding: utf-8 -*-
#
-# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# This file is part of MIL/PEER, licensed under GNU Affero GPLv3 or later.
# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
#
-from django.contrib.auth.models import User
+from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _
-from catalogue.models import Chunk
+from dvcs.models import Revision
-class BookPublishRecord(models.Model):
- """
- A record left after publishing a Book.
- """
+class PublishRecord(models.Model):
+ """A record left after publishing a Document."""
- book = models.ForeignKey('Book', verbose_name=_('book'), related_name='publish_log')
+ document = models.ForeignKey('Document', verbose_name=_('document'), related_name='publish_log')
+ revision = models.ForeignKey(Revision, verbose_name=_('revision'), related_name='publish_log')
timestamp = models.DateTimeField(_('time'), auto_now_add=True)
- user = models.ForeignKey(User, verbose_name=_('user'))
+ user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'))
class Meta:
- app_label = 'catalogue'
ordering = ['-timestamp']
verbose_name = _('book publish record')
verbose_name = _('book publish records')
-
-
-class ChunkPublishRecord(models.Model):
- """
- BookPublishRecord details for each Chunk.
- """
-
- book_record = models.ForeignKey(BookPublishRecord, verbose_name=_('book publish record'))
- change = models.ForeignKey(Chunk.change_model, related_name='publish_log', verbose_name=_('change'))
-
- class Meta:
- app_label = 'catalogue'
- verbose_name = _('chunk publish record')
- verbose_name = _('chunk publish records')
-{% load compressed i18n %}
+{% load i18n %}
{% load catalogue %}
+{% load javascript stylesheet from pipeline %}
+{% load static from staticfiles %}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
- {% compressed_css 'catalogue' %}
- <title>{% block title %}{% trans "Platforma Redakcyjna" %}{% endblock title %}</title>
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ {% stylesheet 'catalogue' %}
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
+ <title>{% block title %}MIL/PEER{% endblock title %}</title>
{% block add_css %}{% endblock %}
+ <style>
+ tr.member-owner {
+ font-weight: bold;
+ }
+ tr.member-pending {
+ font-style: italic;
+ color: #888;
+ }
+ tr.member-pending form {
+ font-style: normal;
+ vertical-align: middle;
+ }
+ </style>
</head>
<body>
-<div id="tabs-nav">
-
- <a href="{% url 'catalogue_document_list' %}">
- <img src="http://edukacjamedialna.edu.pl/static/img/logo.png" id="logo" width="70px">
+<nav class="navbar navbar-default" id="main-navbar" style="color: #ddd; background: #444; box-shadow: 0 0 5px #888; border-color: #888">
+ <div class="container-fluid">
+ <a class="navbar-brand" href="/">
+ <span style="font-weight: bold; color: #ddd"><span style="color: orange">MIL</span>/<span style="color: #0d0">PEER</span></span>
</a>
+ <a target="_blank" href='//evensfoundation.be'><img src="{% static "img/evens.png" %}" style="height: 36px; margin: 7px 20px 7px 30px"></a>
+ <a target="_blank" href='//nowoczesnapolska.org.pl'><img src="{% static "img/fnp.png" %}" style="height: 30px; margin: 10px"></a>
- <div id="tabs-nav-left">
- {% main_tabs %}
+ {% include "registration/head_login.html" %}
</div>
-
- <span id="login-box">
- {% include "registration/head_login.html" %}
- </span>
-
- <div class='clr' ></div>
-</div>
+</nav>
<div id="content">
{% block content %}
-<div id="catalogue_layout_left_column">
- {% block leftcolumn %}
- {% endblock leftcolumn %}
+
+<div class="row">
+<div class="col-md-8 col-md-offset-2">
+
+{% block inner_content %}
+{% endblock %}
+
</div>
-<div id="catalogue_layout_right_column">
- {% block rightcolumn %}
- {% endblock rightcolumn %}
</div>
+
{% endblock content %}
</div>
-<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
-{% compressed_js 'catalogue' %}
+<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
+<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
+{% javascript 'catalogue' %}
{% block add_js %}{% endblock %}
+ <!-- Piwik -->
+ <script type="text/javascript">
+ var pkBaseURL = "//piwik.nowoczesnapolska.org.pl/nocas/";
+ document.write(unescape("%3Cscript src='" + pkBaseURL + "piwik.js' type='text/javascript'%3E%3C/script%3E"));
+ </script><script type="text/javascript">
+ try {
+ var piwikTracker = Piwik.getTracker(pkBaseURL + "piwik.php", 23);
+ piwikTracker.trackPageView();
+ piwikTracker.enableLinkTracking();
+ } catch( err ) {}
+ </script><noscript><p><img src="//piwik.nowoczesnapolska.org.pl/nocas/piwik.php?idsite=23" style="border:0" alt="" /></p></noscript>
+ <!-- End Piwik Tracking Code -->
{% block extrabody %}
{% endblock %}
</body>
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+
+{% block inner_content %}
+ <a href="{% url 'catalogue_user' %}">{% trans "My resources" %}</a> /
+ {% if doc.owner_organization %}
+ <a href="{% url 'organizations_main' doc.owner_organization.pk %}">{{ doc.owner_organization }}</a>
+ {% else %}
+ {{ doc.owner_user.first_name }} {{ doc.owner_user.last_name }}
+ {% endif %} /
+ <h1 style='margin-bottom: 1em'>{{ doc.meta.title }}</h1>
+
+ <p>{% trans "Really delete the resource?" %}</p>
+
+ <form enctype="multipart/form-data" method="POST">
+ {% csrf_token %}
+ <button style="margin-top:1em;" class="btn btn-danger" type="submit">{% trans "Delete" %}</button>
+ <a style="margin-top:1em;" class="btn btn-default" href="{% url 'catalogue_html' doc.pk %}">{% trans "Cancel" %}</a>
+ </form>
+
+{% endblock %}
{% extends "catalogue/base.html" %}
-{% load book_list comments i18n %}
+{% load book_list i18n %}
-{% block content %}
+{% block inner_content %}
-<h1>{{ book.title }}</h1>
+<h1 style="margin-bottom: 1em">{{ book.title }}</h1>
+
+
+{% if tab == 'plan' %}
+
+ <ul class="nav nav-tabs">
+ <li role="presentation" class="active"><a href="#">{% trans "Schedule" %}</a></li>
+ </ul>
+
+{% endif %}
+
+{% comment %}
{% if editable %}<form method='POST'>{% csrf_token %}{% endif %}
<div class='section'>
- <h2>{% trans "Chunks" %}</h2>
+ <h2>{% trans "Edit" %}</h2>
<table class='single-book-list'><tbody>
{% for chunk in book %}
</div>
-
<div class='section'>
</div>
+{% endcomment %}
-<div class='section'>
- <h2>{% trans "Comments" %}</h2>
-
- {% render_comment_list for book %}
- {% with book.get_absolute_url as next %}
- {% render_comment_form for book %}
- {% endwith %}
-</div>
-
-{% endblock content %}
+{% endblock %}
{% load i18n %}
-{% if book.single %}
- {% with book.0 as chunk %}
<tr>
- <td><input type="checkbox" name="select_book" value="{{book.id}}" data-chunk-id="{{chunk.id}}"/></td>
- <td><a target="_blank"
- href="{% url 'wiki_editor' book.slug %}">
- {{ book.title }}</a></td>
- <td>{% if chunk.stage %}
- {{ chunk.stage }}
- {% else %}–
- {% endif %}</td>
- <td class='user-column'>{% if chunk.user %}<a href="{% url 'catalogue_user' chunk.user.username %}">{{ chunk.user.first_name }} {{ chunk.user.last_name }}</a>{% endif %}</td>
- <td>{{ book.project.name }}</td>
- <td><a href="{% url 'catalogue_book_gallery' book.slug %}">Materiały</a></td>
+ <td><a href="{% url 'catalogue_html' book.pk %}">{{ book.meta.title }}</a></td>
+ <td>
+ {% if book.owner_user %}
+ {{ book.owner_user.first_name }} {{ book.owner_user.last_name }}
+ {% else %}
+ <a href="{{ book.owner_organization.get_absolute_url }}">{{ book.owner_organization }}</a>
+ {% endif %}
+ {% if am_owner %}
+ <a href="{% url 'catalogue_book_owner' book.pk %}">({% trans "change" %})</a>
+ {% endif %}
+ </td>
+ <td>{{ book.stage|default:"–" }}</td>
+ <td class="{% if book.is_overdue %}alert-danger{% endif %}">{% if book.assigned_to %}{{ book.assigned_to.first_name }} {{ book.assigned_to.last_name }}{% endif %}</td>
+ <td class="{% if book.is_overdue %}alert-danger{% endif %}">{{ book.get_plan.deadline|default:"–" }}</td>
+ <td>
+ {% if am_owner %}
+ <div class="btn-group-vertical">
+ <a class="btn btn-default" href="{% url 'wiki_editor' book.pk %}">{% trans "Edit" %}</a>
+ <a class="btn btn-default" href="{% url 'catalogue_book_schedule' book.pk %}">{% trans "Schedule" %}</a>
+ <a class="btn btn-default" href="{% url 'catalogue_book_delete' book.pk %}">{% trans "Delete" %}</a>
+ </div>
+ {% endif %}
+ </td>
</tr>
- {% endwith %}
-{% else %}
- <tr>
- <td><input type="checkbox" name="select_book" value="{{book.id}}"/></td>
- <td>{{ book.title }}</td>
- <td></td>
- <td class='user-column'></td>
- <td>{{ book.project.name }}</td>
- <td></td>
- </tr>
-{% endif %}
+
+</div>
+</div>
{% load i18n %}
{% load pagination_tags %}
+{% load document_short_html from document_list %}
-
-<form name='filter' action=''>
-<input type='hidden' name="title" value="{{ request.GET.title }}" />
-<input type='hidden' name="stage" value="{{ request.GET.stage }}" />
-{% if not viewed_user %}
- <input type='hidden' name="user" value="{{ request.GET.user }}" />
-{% endif %}
-<input type='hidden' name="all" value="{{ request.GET.all }}" />
-<input type='hidden' name="status" value="{{ request.GET.status }}" />
-<input type='hidden' name="project" value="{{ request.GET.project }}" />
-</form>
-
-
-<table id="file-list"{% if viewed_user %} class="book-list-user"{% endif %}>
- <thead><tr>
- <th></th>
- <th class='book-search-column'>
- <form>
- <input title='Szukaj w tytułach modułów' name="title"
- class='text-filter' value="{{ request.GET.title }}" />
- </form>
- </th>
- <th><select name="stage" class="filter">
- <option value=''>- {% trans "stage" %} -</option>
- <option {% if request.GET.stage == '-' %}selected="selected"
- {% endif %}value="-">- {% trans "none" %} -</option>
- {% for stage in stages %}
- <option {% if request.GET.stage == stage.slug %}selected="selected"
- {% endif %}value="{{ stage.slug }}">{{ stage.name }}</option>
- {% endfor %}
- </select></th>
-
- {% if not viewed_user %}
- <th><select name="user" class="filter">
- <option value=''>- {% trans "editor" %} -</option>
- <option {% if request.GET.user == '-' %}selected="selected"
- {% endif %}value="-">- {% trans "none" %} -</option>
- {% for user in users %}
- <option {% if request.GET.user == user.username %}selected="selected"
- {% endif %}value="{{ user.username }}">{{ user.first_name }} {{ user.last_name }} ({{ user.count }})</option>
- {% endfor %}
- </select></th>
- {% endif %}
-
- <th><select name="project" class="filter">
- <option value=''>- poziom edukacyjny -</option>
- <option {% if request.GET.project == '-' %}selected="selected"
- {% endif %}value="-">- {% trans "none" %} -</option>
- {% for project in projects %}
- <option {% if request.GET.project == project.pk|slugify %}selected="selected"
- {% endif %}value='{{ project.pk }}'>{{ project.name }}</option>
- {% endfor %}
- </select></th>
-
- <th></th>
- </tr></thead>
-
- {% with cnt=books|length %}
+<table class="table table-striped">
{% autopaginate books 100 %}
+ <thead>
+ <th>{% trans "Title" %}</th>
+ <th>{% trans "Owner" %}</th>
+ <th>{% trans "Stage" %}</th>
+ <th>{% trans "Assigned to" %}</th>
+ <th>{% trans "Deadline" %}</th>
+ <th></th>
+ </thead>
<tbody>
- {% for item in books %}
- {% with item.book as book %}
- {{ book.short_html|safe }}
- {% if not book.single %}
- {% for chunk in item.chunks %}
- {{ chunk.short_html|safe }}
- {% endfor %}
- {% endif %}
- {% endwith %}
+ {% for doc in books %}
+ {% document_short_html doc %}
{% endfor %}
<tr><th class='paginator' colspan="5">
{% paginate %}
- {% blocktrans count c=cnt %}{{c}} module{% plural %}{{c}} modules{% endblocktrans %}</th></tr>
+ </th></tr>
</tbody>
- {% endwith %}
</table>
{% if not books %}
- <p>Nie znaleziono modułów.</p>
+ <p>{% trans "No resources found." %}</p>
{% endif %}
-<form id='chunk_mass_edit' action='{% url "catalogue_chunk_mass_edit" %}' style="display:none;">
-{% csrf_token %}
-<input type="hidden" name="ids" />
-<label for="mass_edit_stage">{% trans "Set stage" %}</label><input type="hidden" name="stage" id="mass_edit_stage"/>
-<label for="mass_edit_user">{% trans "Set user" %}</label><input type="hidden" name="user" id="mass_edit_stage" />
-<input type="hidden" name="status" />
-<label for="mass_edit_project">Poziom edukacyjny</label><input type="hidden" name="project" id="mass_edit_project" />
-<label for="mass_edit_publish">{% trans "Mark publishable" %}</label>
-<label for="mass_edit_unpublish">{% trans "Mark not publishable" %}</label>
-<label for="mass_edit_other">{% trans "Other user" %}</label>
-</form>
-
<select name="other-user" style="display:none;">
{% for user in other_users %}
<option {% if request.GET.user == user.username %}selected="selected"
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+
+{% block inner_content %}
+ <a href="{% url 'catalogue_user' %}">{% trans "My resources" %}</a> /
+ {% if doc.owner_organization %}
+ <a href="{% url 'organizations_main' doc.owner_organization.pk %}">{{ doc.owner_organization }}</a>
+ {% else %}
+ {{ doc.owner_user.first_name }} {{ doc.owner_user.last_name }}
+ {% endif %} /
+ <h1 style='margin-bottom: 1em'>{{ doc.meta.title }}</h1>
+
+
+ <form enctype="multipart/form-data" method="POST">
+ {% csrf_token %}
+ <label for="owner">{% trans "Owner" %}</label>
+ <select class="form-control" name="owner_organization">
+ <option value=''>{{ request.user }}</option>
+ {% for org in request.user.membership_set.all %}
+ <option value="{{ org.organization.pk }}"
+ {% if doc.owner_organization == org.organization %}selected="selected"{% endif %}
+ >{{ org.organization }}</option>
+ {% endfor %}
+ </select>
+
+ <button style="margin-top:1em;" class="btn btn-default" type="submit">{% trans "Change owner" %}</button></td></tr>
+ </form>
+
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+
+{% block inner_content %}
+
+
+<a style="float:right" class="btn btn-default" href="{% url 'wiki_editor' book.pk %}">{% trans "Edit" %}</a>
+<a style="float:right; margin-right: 5px;" class="btn btn-default" href="{% url 'catalogue_fork' book.pk %}">{% trans "Create another version" %}</a>
+
+<a href="{% url 'catalogue_user' %}">{% trans "My resources" %}</a> /
+{% if book.owner_organization %}
+ <a href="{% url 'organizations_main' book.owner_organization.pk %}">{{ book.owner_organization }}</a>
+{% else %}
+ {{ book.owner_user.first_name }} {{ book.owner_user.last_name }}
+{% endif %} /
+<h1 style="margin-bottom: 1em">{{ book.meta.title }}</h1>
+
+
+<!--ul class="nav nav-tabs">
+ <li role="presentation" class="disabled"><a href="#">Details</a></li>
+ <li role="presentation" class="active"><a href="#">Schedule</a></li>
+ <li role="presentation" class="disabled"><a href="#">Publication</a></li>
+</ul-->
+
+<form method="POST">
+<table class="table table-striped">
+ <thead>
+ <th>{% trans "Stage" %}</th>
+ <th>{% trans "Person" %}</th>
+ <th>{% trans "Deadline" %}</th>
+ </thead>
+ <tbody>
+ {% for i, sch, data in schedule %}
+ <tr>
+ <td>{{ sch }}</td>
+ <td>
+ {% if people|length > 1 %}
+ <select name="s{{ i }}-user" class="form-control">
+ <option value="">---</option>
+ {% for u in people %}
+ <option value="{{ u.pk }}"
+ {% if u.pk == data.0 %}
+ selected="selected"
+ {% endif %}
+ >{{ u.first_name }} {{ u.last_name }}</option>
+ {% endfor %}
+ </select>
+ {% else %}
+ {{ people.0.first_name }} {{ people.0.last_name }}
+ {% endif %}
+ </td>
+ <td><input placeholder="YYYY-MM-DD" name="s{{ i }}-deadline" value="{{ data.1|default:'' }}" class="form-control datepicker-field"></td>
+ </tr>
+ {% endfor %}
+
+ <tr>
+ <td>{% trans "Current stage" %}</td>
+ <td colspan="2"><select name="stage" class="form-control">
+ <option value="">–</option>
+ {% for i, sch, data in schedule %}
+ <option value="{{ sch }}"
+ {% if sch == book.stage %}
+ selected="selected"
+ {% endif %}
+ >{{ sch }}</option>
+ {% endfor %}
+ </select>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="3"><button type="submit" class="btn btn-default">{% trans "Update" %}</button></td>
+ </tr>
+ </tbody>
+</table>
+{% csrf_token %}
+</form>
+
+
+{% endblock %}
-{% load i18n compressed %}
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
- <title>{% trans "Redakcja" %} :: {{ book.title }}</title>
- {% compressed_css 'book' %}
- <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
- {% compressed_js 'book' %}
- </head>
- <body>
- <div id="menu">
- <ul>
- <li><a class="menu" href="#toc">{% trans "Table of contents" %}</a></li>
-{# <li><a class="menu" href="#themes">{% trans "Themes" %}</a></li>#}
- <li><a class="menu" href="#nota_red">{% trans "Edit. note" %}</a></li>
-{# <li><a class="menu" href="#info">{% trans "Infobox" %}</a></li>#}
-{# <li><a href="{{ book.get_absolute_url }}">{% trans "Book's page" %}</a></li> #}
-{# <li><a class="menu" href="#download">{% trans "Download" %}</a></li>#}
- </ul>
+{% extends "catalogue/base.html" %}
+{% load i18n pipeline catalogue_files %}
+
+{% block title %}{{ sst.meta.title }}{% endblock %}
+
+{% load thumbnail %}
+
+{% block content %}
+<div class="navbar navbar-default row" style="margin-top:-20px;">
+ <div class="col-md-8 col-md-offset-2">
+
+{% if doc.owner_organization %}
+
+{% if doc.owner_organization.logo %}
+
+{% thumbnail doc.owner_organization.logo '80x50' format='PNG' as th %}
+<a class="navbar-brand" href="{{ doc.owner_organization.get_absolute_url }}" style="padding: 0;">
+ <img src="{{ th.url }}" style="padding: {{ th|margin:'80x50' }}" title="{{ doc.owner_organization }}">
+</a>
+{% endthumbnail %}
+
+{% else %}
+<span class="navbar-text">{% trans "Resource owner" %}:</span>
+<a class="navbar-text" href="{{ doc.owner_organization.get_absolute_url }}">
+ {{ doc.owner_organization }}
+</a>
+{% endif %}
+
+{% else %}
+<div class="navbar-text" href="">{% trans "Resource owner" %}: {{ doc.owner_user.first_name }} {{ doc.owner_user.last_name }}</div>
+{% endif %}
+
+<div class="btn-group navbar-right">
+
+{% if can_edit %}
+<a class="btn btn-default navbar-btn" href="{% url 'wiki_editor' doc.pk %}"
+ data-toggle="tutorial" data-tutorial="3" data-placement="bottom"
+ data-content="{% trans "You can edit your resource here." %}"
+>Edit</a>
+<a class="btn btn-default navbar-btn" href="{% url 'catalogue_book_schedule' doc.pk %}"
+ data-toggle="tutorial" data-tutorial="4" data-placement="bottom"
+ data-content="{% trans "You can assign work stages and deadlines to people on your team." %}"
+>Schedule</a>
+<a class="btn btn-default navbar-btn" href="{% url 'catalogue_fork' doc.pk %}"
+ data-toggle="tutorial" data-tutorial="5" data-placement="bottom"
+ data-content="{% trans "You can also create another, independent version of this resource – e.g. for translation to other language or simply for adapting it to your needs." %}"
+>{% trans "Create another version" %}</a>
+<a class="btn btn-default navbar-btn" href="{% url 'catalogue_book_owner' doc.pk %}">{% trans "Change owner" %}</a>
+<a class="btn btn-default navbar-btn" href="{% url 'catalogue_book_delete' doc.pk %}">{% trans "Delete" %}</a>
+
+{% elif doc %}
+<a class="navbar-right btn btn-default navbar-btn" href="{% url 'catalogue_fork' doc.pk %}"
+ data-toggle="tutorial" data-tutorial="5" data-placement="bottom"
+ data-content="{% trans "You can create and edit another, independent version of this resource – e.g. for translation to other language or simply for adapting it to your needs." %}"
+>{% trans "Create another version" %}</a>
+{% endif %}
+
+</div>
+
+</div>
+</div>
+
+{% if preview or can_edit and revision == published_revision %}
+<div class="jumbotron" style="margin-top:-20px; margin-bottom: 20px;">
+<p style="text-align: center">
+
+{% if specific %}
+{% url 'catalogue_html' doc.pk as url %}
+{% blocktrans %}This is a preview of a specific revision of <a href="{{ url }}">this resource</a>.{% endblocktrans %}<br>
+{% endif %}
+
+{% if revision.pk != doc.revision.pk and doc.revision.pk != published_revision.pk %}
+{% url 'catalogue_preview' doc.pk as url %}
+{% blocktrans %}There have been some changes since this revision. <a href="{{ url }}">See the current revision.</a>{% endblocktrans %}<br>
+{% endif %}
+
+{% if published_revision %}
+ {% if published_revision != revision %}
+ {% url 'catalogue_html' doc.pk as url %}
+ {% blocktrans %}This resource has a <a href="{{ url }}">published version</a>.{% endblocktrans %}
+ {% endif %}
+{% else %}
+ {% trans "This resource hasn't been published yet." %}
+{% endif %}
+
+</p>
+
+{% if can_edit and not revision == published_revision %}
+ <form onsubmit="return confirm('{% trans "Do you really want to publish this revision?" %}');" method="POST" action="{% url 'catalogue_publish' doc.pk %}" style="text-align: center;">
+ {% csrf_token %}
+ <input type="hidden" name="textpublish-revision" value="{{ revision.pk }}">
+ <button class="btn btn-default btn-warning"
+ data-toggle="tutorial" data-tutorial="1" data-placement="bottom"
+ data-content="{% trans "When the resource is ready for use, you can mark its specific revision as published here." %}"
+>{% trans "Publish this revision" %}</button>
+ </form>
+{% endif %}
+{% if can_edit and revision == published_revision %}
+ <form onsubmit="return confirm('{% trans "Do you really want to undo publishing this resource?" %}');" method="POST" action="{% url 'catalogue_unpublish' doc.pk %}" style="text-align: center;">
+ {% csrf_token %}
+ <button class="btn btn-default btn-danger"
+ data-toggle="tutorial" data-tutorial="1" data-placement="bottom"
+ data-content="{% trans "If you have published the resource by mistake, you can un-publish it here." %}"
+ >{% trans "Undo publishing this resource" %}</button>
+ </form>
+{% endif %}
+
+</div>
+{% endif %}
+
+
+
+<style>
+ #cover {
+ margin-top: -20px;
+ display: block;
+ width: 100%;
+ position: relative;
+
+{% if doc.meta.cover_url %}
+ height: 300px;
+ background-image: url('{{ doc.meta.cover_url|as_media_for:doc }}');
+ background-repeat: no-repeat;
+ background-size: 100%;
+ background-position: 0 15%;
+{% else %}
+ height: 150px;
+{% endif %}
+ }
+ #cover-info {
+ position: absolute;
+ left:0;
+ right:0;
+ bottom: 0;
+ margin: 0;
+ padding: 3em 0 2em 0;
+ color: white;
+/* IE9 SVG, needs conditional override of 'filter' to 'none' */
+background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIgc3RvcC1vcGFjaXR5PSIwIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMwMDAwMDAiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
+background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%); /* FF3.6+ */
+background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,0,0,0)), color-stop(100%,rgba(0,0,0,1))); /* Chrome,Safari4+ */
+background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Chrome10+,Safari5.1+ */
+background: -o-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Opera 11.10+ */
+background: -ms-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* IE10+ */
+background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* W3C */
+filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#000000',GradientType=0 ); /* IE6-8 */
+ }
+</style>
+<div id="cover">
+ <div id="cover-info" class="row">
+ <div class="col-md-4 col-md-offset-2">
+{% trans "Audience" %}: {{ doc.meta.audience|default:"–" }}
</div>
- <div id="header">
- <a href="/"><img src="/media/static/img/logo-220.png" alt="Wolne Lektury" /></a>
+ <div class="col-md-4">
+<a class="btn btn-default" href="{% url 'catalogue_pdf' doc.pk revision.pk %}"
+ data-toggle="tutorial" data-tutorial="2" data-placement="top"
+ data-content="{% trans "You can download and share a PDF version – and more formats in the future." %}"
+
+>{% trans "Download PDF" %}</a>
</div>
+ </div>
+</div>
+
+<div id="digcontent"
+ data-toggle="tutorial" data-tutorial="9" data-placement="top"
+ data-content="{% trans "And, of course, you can read and share the resource in a handy webpage form right here." %}"
+>
+ {{ block.super }}
+</div>
+
+
+
+
+{% endblock %}
+
+{% block inner_content %}
{{ html|safe }}
- </body>
-</html>
+{% endblock %}
{% extends "catalogue/base.html" %}
{% load i18n %}
-{% block content %}
- <h1>Utwórz nowy moduł</h1>
+{% block inner_content %}
+ <h1>{% trans "Create a new resource" %}</h1>
+
<form enctype="multipart/form-data" method="POST">
{% csrf_token %}
- <table class='editable'>
- {{ form.as_table}}
- <tr><td></td><td><button type="submit">Utwórz moduł</button></td></tr>
- </table>
+ {{ form.non_field_errors }}
+ <label for="owner">{% trans "Owner" %}</label>
+ {{ form.owner_organization.errors }}
+ <select class="form-control" name="owner_organization"
+ data-toggle="tutorial" data-tutorial="1" data-placement="bottom"
+ data-content="{% trans 'You can choose whether the resource should be owned by you or by your organization. This can be changed later.' %}"
+ >
+ <option>{{ request.user }}</option>
+ {% for org in request.user.membership_set.all %}
+ <option value="{{ org.organization.pk }}"
+ {% if form.owner_organization.value == org.organization.pk %}selected="selected"{% endif %}
+ >{{ org.organization }}</option>
+ {% endfor %}
+ </select>
+ <label for="title">{% trans "Title" %}</label>
+ {{ form.title.errors }}
+ <input class="form-control" name="title" type="text" value='{{ form.title.value|default:"" }}'>
+ <label for="title">{% trans "Cover image" %}</label>
+ {{ form.cover.errors }}
+ {{ form.cover }}
+ <label for="language">{% trans "Language" %}</label>
+ {{ form.language.errors }}
+ <input class="form-control" name="language" type="text" value='{{ form.language.value|default:"" }}'>
+ <label for="publisher">{% trans "Publisher" %}</label>
+ {{ form.publisher.errors }}
+ <input class="form-control" name="publisher" type="text" value='{{ form.publisher.value|default:"" }}'>
+ <label for="publisher">{% trans "Rights" %}</label>
+ {{ form.rights.errors }}
+ <select class="form-control" name="rights"
+ data-toggle="tutorial" data-tutorial="2" data-placement="bottom"
+ data-content="{% trans 'You should choose a free license for your resource. We recommend using Creative Commons Attribution - Share Alike.' %}"
+ >
+ <option value=''>–</option>
+ <option name='pd'
+ data-help="{% trans 'Only set for resources that are not restricted with copyright.' %}"
+ >{% trans "public domain" %}</option>
+ <option name='cc-by'
+ data-help="{% trans "Non-copyleft free culture license. See <a target='_blank' href='//creativecommons.org/choose/'>creativecommons.org</a>" %}"
+ >{% trans "Creative Commons Attribution" %}</option>
+ <option name='cc-by-sa'
+ data-help="{% trans "Copyleft free culture license. See <a target='_blank' href='//creativecommons.org/choose/'>creativecommons.org</a>" %}"
+ >{% trans "Creative Commons Attribution – Share Alike" %}</option>
+ <option name='fal'
+ data-help="{% trans "Copyleft free culture license. See <a target='_blank' href='http://artlibre.org/'>artlibre.org</a>" %}"
+ >{% trans "Free Art License" %}</option>
+ </select>
+ <div class="help-text" style="text-align: right;"></div>
+ <label for="audience">{% trans "Audience" %}</label>
+ {{ form.audience.errors }}
+ <select class="form-control" name="audience"
+ data-toggle="tutorial" data-tutorial="3" data-placement="bottom"
+ data-content="{% trans 'Choose primary audience for your resource.' %}"
+ >
+ <option>3-6</option>
+ <option>6-9</option>
+ <option>9-12</option>
+ <option>12-18</option>
+ <option>18+</option>
+ <option>Adults</option>
+ </select>
+
+ <label for="description">{% trans "Summary" %}</label>
+ {{ form.description.errors }}
+ <textarea class="form-control" name="description"
+ data-toggle="tutorial" data-tutorial="4" data-placement="bottom"
+ data-content="{% trans 'You can provide a short description of the document here.' %}"
+ >{{ form.description.value|default:"" }}</textarea>
+ {#{ form.as_table}#}
+
+ <button style="margin-top:1em;" class="btn btn-default" type="submit">{% trans "Create resource" %}</button></td></tr>
</form>
-{% endblock content %}
+
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+
+{% block inner_content %}
+ <h1>{% trans "Create another version of a resource" %}</h1>
+
+
+ <form enctype="multipart/form-data" method="POST">
+ {% csrf_token %}
+ {{ form.non_field_errors }}
+ <label for="owner">{% trans "Create as" %}</label>
+ {{ form.owner_organization.errors }}
+ <select class="form-control" name="owner_organization">
+ <option>{{ request.user }}</option>
+ {% for org in request.user.membership_set.all %}
+ <option value="{{ org.organization.pk }}">{{ org.organization }}</option>
+ {% endfor %}
+ </select>
+
+ <button style="margin-top:1em;" class="btn btn-default" type="submit">{% trans "Create another version" %}</button></td></tr>
+ </form>
+
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n thumbnail catalogue_files %}
+
+
+{% block inner_content %}
+
+
+<h1>{% trans "Finished resources" %}</h1>
+{% for doc in objects_list %}
+ <div class="resource-box" style="width: 182px; height: 180px; position: relative; border: 1px solid #aaa; margin: 10px; display: inline-block; padding: 0px; vertical-align: bottom">
+ <a style='display:block' href="{% url 'catalogue_html' doc.pk %}">
+ {% if doc.meta.cover_url %}
+ {% thumbnail doc.meta.cover_url|as_media_for:doc "180x120" crop="center" format="PNG" as th %}
+ <img src="{{ th.url }}">
+ {% endthumbnail %}
+ {% elif doc.owner_organization.logo %}
+ {% thumbnail doc.owner_organization.logo "160x100" format="PNG" padding=True as th %}
+ <img src="{{ th.url }}">
+ {% endthumbnail %}
+ {% endif %}
+ <div style="position: absolute; bottom: 10px; left: 10px; width: 160px; height: 40px; overflow:hidden">{{ doc.meta.title }}</div>
+ <div class="box-overlay" style="position: absolute; top:0; left: 0; padding: 5px; width: 180px; height: 120px; background: black; color: white; opacity: .7">
+ {% if doc.meta.audience %}
+ {% trans "Audience" %}: {{ doc.meta.audience }}<br>
+ {% endif %}
+ {% trans "Owner" %}: {{ doc.owner_organization|default:"" }}
+ {{ doc.owner_user.first_name }} {{ doc.owner_user.last_name }}<br/>
+ </div>
+ </a>
+ </div>
+{% endfor %}
+
+
+{% endblock %}
{% extends "catalogue/base.html" %}
{% load i18n %}
-{% load catalogue book_list wall %}
-{% load compressed %}
+{% load catalogue document_list %}
-{% block add_js %}
-{% compressed_js 'book_list' %}
-{% endblock %}
+{% block content %}
+<div class="row">
+<div class="col-md-8 col-md-offset-2">
+
+ <a style="float:right" class="btn btn-default" href="{% url 'catalogue_create_missing' %}">{% trans "New resource +" %}</a>
+
+ <h1 style="margin-bottom: 1em">{% trans "My resources" %}</h1>
+
+ <!--ul class="nav nav-tabs">
+ <li role="presentation" class="active"><a href="#">{% trans "Projects" %}</a></li>
+ </ul-->
+
+ {% document_list user=user %}
+
+</div>
+</div>
-{% block add_css %}
-{% compressed_css 'book_list' %}
{% endblock %}
-{% block leftcolumn %}
- {% book_list request.user %}
-{% endblock leftcolumn %}
-
-{% block rightcolumn %}
- <div id="last-edited-list">
- <h2>{% trans "Your last edited documents" %}</h2>
- <ol>
- {% for slugs, item in last_books %}
- <li><a href="{% url 'wiki_editor' slugs.0 slugs.1 %}"
- target="_blank">{{ item.title }}</a><br/><span class="date">({{ item.time|date:"H:i:s, d/m/Y" }})</span></li>
- {% endfor %}
- </ol>
- </div>
-
- <h2>{% trans "Recent activity for" %} {{ request.user|nice_name }}</h2>
- {% wall request.user 10 %}
-{% endblock rightcolumn %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+{% load thumbnail catalogue_files %}
+
+
+{% block inner_content %}
+
+<h1>{% trans "Upcoming resources" %}</h1>
+{% for doc in objects_list %}
+ <div class="resource-box" style="width: 182px; height: 180px; position: relative; border: 1px solid #aaa; margin: 10px; display: inline-block; padding: 0px; vertical-align: bottom">
+ <a style='display:block' href="{% url 'catalogue_preview' doc.pk %}">
+ {% if doc.meta.cover_url %}
+ {% thumbnail doc.meta.cover_url|as_media_for:doc "180x120" crop="center" format="PNG" as th %}
+ <img src="{{ th.url }}">
+ {% endthumbnail %}
+ {% elif doc.owner_organization.logo %}
+ {% thumbnail doc.owner_organization.logo "160x100" format="PNG" padding=True as th %}
+ <img src="{{ th.url }}" style="margin: 10px;">
+ {% endthumbnail %}
+ {% endif %}
+ <div style="position: absolute; bottom: 10px; left: 10px; width: 160px; height: 40px; overflow:hidden">{{ doc.meta.title }}</div>
+ <div class="box-overlay" style="position: absolute; top:0; left: 0; padding: 5px; width: 180px; height: 120px; background: black; color: white; opacity: .7">
+ {% if doc.meta.audience %}
+ {% trans "Audience" %}: {{ doc.meta.audience }}<br>
+ {% endif %}
+ {% trans "Owner" %}: {{ doc.owner_organization|default:"" }}
+ {{ doc.owner_user.first_name }} {{ doc.owner_user.last_name }}<br/>
+ </div>
+ </a>
+ </div>
+{% endfor %}
+
+{% endblock %}
+++ /dev/null
-from __future__ import absolute_import
-
-from re import split
-from django.db.models import Q, Count
-from django import template
-from django.utils.translation import ugettext_lazy as _
-from django.contrib.auth.models import User
-from catalogue.models import Chunk, Project
-
-register = template.Library()
-
-
-class ChunksList(object):
- def __init__(self, chunk_qs):
- #self.chunk_qs = chunk_qs#.annotate(
- #book_length=Count('book__chunk')).select_related(
- #'book')#, 'stage__name',
- #'user')
- self.chunk_qs = chunk_qs.select_related('book__hidden')
-
- self.book_qs = chunk_qs.values('book_id')
-
- def __getitem__(self, key):
- if isinstance(key, slice):
- return self.get_slice(key)
- elif isinstance(key, int):
- return self.get_slice(slice(key, key+1))[0]
- else:
- raise TypeError('Unsupported list index. Must be a slice or an int.')
-
- def __len__(self):
- return self.book_qs.count()
-
- def get_slice(self, slice_):
- book_ids = [x['book_id'] for x in self.book_qs[slice_]]
- chunk_qs = self.chunk_qs.filter(book__in=book_ids)
-
- chunks_list = []
- book = None
- for chunk in chunk_qs:
- if chunk.book != book:
- book = chunk.book
- chunks_list.append(ChoiceChunks(book, [chunk]))
- else:
- chunks_list[-1].chunks.append(chunk)
- return chunks_list
-
-
-class ChoiceChunks(object):
- """
- Associates the given chunks iterable for a book.
- """
-
- chunks = None
-
- def __init__(self, book, chunks):
- self.book = book
- self.chunks = chunks
-
-
-def foreign_filter(qs, value, filter_field, model, model_field='slug', unset='-'):
- if value == unset:
- return qs.filter(**{filter_field: None})
- if not value:
- return qs
- try:
- obj = model._default_manager.get(**{model_field: value})
- except model.DoesNotExist:
- return qs.none()
- else:
- return qs.filter(**{filter_field: obj})
-
-
-def search_filter(qs, value, filter_fields):
- if not value:
- return qs
- q = Q(**{"%s__icontains" % filter_fields[0]: value})
- for field in filter_fields[1:]:
- q |= Q(**{"%s__icontains" % field: value})
- return qs.filter(q)
-
-
-_states = [
- ('publishable', _('publishable'), Q(book___new_publishable=True)),
- ('changed', _('changed'), Q(_changed=True)),
- ('published', _('published'), Q(book___published=True)),
- ('unpublished', _('unpublished'), Q(book___published=False)),
- ('empty', _('empty'), Q(head=None)),
- ]
-_states_options = [s[:2] for s in _states]
-_states_dict = dict([(s[0], s[2]) for s in _states])
-
-
-def document_list_filter(request, **kwargs):
-
- def arg_or_GET(field):
- return kwargs.get(field, request.GET.get(field))
-
- if arg_or_GET('all'):
- chunks = Chunk.objects.all()
- else:
- chunks = Chunk.visible_objects.all()
-
- chunks = chunks.order_by('book__title', 'book', 'number')
-
- if not request.user.is_authenticated():
- chunks = chunks.filter(book__public=True)
-
- state = arg_or_GET('status')
- if state in _states_dict:
- chunks = chunks.filter(_states_dict[state])
-
- chunks = foreign_filter(chunks, arg_or_GET('user'), 'user', User, 'username')
- chunks = foreign_filter(chunks, arg_or_GET('stage'), 'stage', Chunk.tag_model, 'slug')
- chunks = search_filter(chunks, arg_or_GET('title'), ['book__title', 'title'])
- chunks = foreign_filter(chunks, arg_or_GET('project'), 'book__project', Project, 'pk')
- return chunks
-
-
-@register.inclusion_tag('catalogue/book_list/book_list.html', takes_context=True)
-def book_list(context, user=None):
- request = context['request']
-
- if user:
- filters = {"user": user}
- new_context = {"viewed_user": user}
- else:
- filters = {}
- new_context = {
- "users": User.objects.annotate(
- count=Count('chunk')).filter(count__gt=0).order_by(
- '-count', 'last_name', 'first_name'),
- "other_users": User.objects.annotate(
- count=Count('chunk')).filter(count=0).order_by(
- 'last_name', 'first_name'),
- }
-
- new_context.update({
- "filters": True,
- "request": request,
- "books": ChunksList(document_list_filter(request, **filters)),
- "stages": Chunk.tag_model.objects.all(),
- "states": _states_options,
- "projects": Project.objects.all(),
- })
-
- return new_context
tabs = []
user = context['user']
- tabs.append(Tab('my', _('My page'), reverse("catalogue_user")))
+ #tabs.append(Tab('my', _('My page'), reverse("catalogue_user")))
- tabs.append(Tab('activity', _('Activity'), reverse("catalogue_activity")))
- tabs.append(Tab('all', _('All'), reverse("catalogue_document_list")))
- tabs.append(Tab('users', _('Users'), reverse("catalogue_users")))
+ #tabs.append(Tab('activity', _('Activity'), reverse("catalogue_activity")))
+ #tabs.append(Tab('all', _('All'), reverse("catalogue_document_list")))
+ #tabs.append(Tab('users', _('Users'), reverse("catalogue_users")))
- if user.has_perm('catalogue.add_book'):
- tabs.append(Tab('create', _('Add'), reverse("catalogue_create_missing")))
+ #if user.has_perm('catalogue.add_book'):
+ # tabs.append(Tab('create', _('Add'), reverse("catalogue_create_missing")))
return {"tabs": tabs, "active_tab": active}
--- /dev/null
+from django import template
+from django.conf import settings
+register = template.Library()
+
+
+@register.filter
+def as_media_for(uri, document):
+ if uri.startswith('file://'):
+ uri = "https://milpeer.eu%suploads/%d/%s" % (settings.MEDIA_URL, document.pk, uri[len('file://'):])
+ return uri
+
--- /dev/null
+from __future__ import absolute_import
+
+from re import split
+from django.db.models import Q, Count
+from django import template
+from django.utils.translation import ugettext_lazy as _
+from django.contrib.auth.models import User
+from catalogue.models import Document
+
+register = template.Library()
+
+
+class ChunksList(object):
+ def __init__(self, chunk_qs):
+ #self.chunk_qs = chunk_qs#.annotate(
+ #book_length=Count('book__chunk')).select_related(
+ #'book')#, 'stage__name',
+ #'user')
+ self.chunk_qs = chunk_qs.select_related('book__hidden')
+
+ self.book_qs = chunk_qs.values('book_id')
+
+ def __getitem__(self, key):
+ if isinstance(key, slice):
+ return self.get_slice(key)
+ elif isinstance(key, int):
+ return self.get_slice(slice(key, key+1))[0]
+ else:
+ raise TypeError('Unsupported list index. Must be a slice or an int.')
+
+ def __len__(self):
+ return self.book_qs.count()
+
+ def get_slice(self, slice_):
+ book_ids = [x['book_id'] for x in self.book_qs[slice_]]
+ chunk_qs = self.chunk_qs.filter(book__in=book_ids)
+
+ chunks_list = []
+ book = None
+ for chunk in chunk_qs:
+ if chunk.book != book:
+ book = chunk.book
+ chunks_list.append(ChoiceChunks(book, [chunk]))
+ else:
+ chunks_list[-1].chunks.append(chunk)
+ return chunks_list
+
+
+class ChoiceChunks(object):
+ """
+ Associates the given chunks iterable for a book.
+ """
+
+ chunks = None
+
+ def __init__(self, book, chunks):
+ self.book = book
+ self.chunks = chunks
+
+
+def foreign_filter(qs, value, filter_field, model, model_field='slug', unset='-'):
+ #print qs, value, filter_field, model, model_field, unset
+ if value == unset:
+ return qs.filter(**{filter_field: None})
+ if not value:
+ return qs
+ try:
+ obj = model._default_manager.get(**{model_field: value})
+ except model.DoesNotExist:
+ return qs.none()
+ else:
+ return qs.filter(**{filter_field: obj})
+
+
+def search_filter(qs, value, filter_fields):
+ if not value:
+ return qs
+ q = Q(**{"%s__icontains" % filter_fields[0]: value})
+ for field in filter_fields[1:]:
+ q |= Q(**{"%s__icontains" % field: value})
+ return qs.filter(q)
+
+
+_states = [
+ ('publishable', _('publishable'), Q(book___new_publishable=True)),
+ ('changed', _('changed'), Q(_changed=True)),
+ ('published', _('published'), Q(book___published=True)),
+ ('unpublished', _('unpublished'), Q(book___published=False)),
+ ('empty', _('empty'), Q(head=None)),
+ ]
+_states_options = [s[:2] for s in _states]
+_states_dict = dict([(s[0], s[2]) for s in _states])
+
+
+def document_list_filter(request, **kwargs):
+
+ def arg_or_GET(field):
+ return kwargs.get(field, request.GET.get(field))
+
+ if arg_or_GET('all'):
+ chunks = Chunk.objects.all()
+ else:
+ chunks = Chunk.visible_objects.all()
+
+ chunks = chunks.order_by('book__title', 'book', 'number')
+
+ if not request.user.is_authenticated():
+ chunks = chunks.filter(book__public=True)
+
+ state = arg_or_GET('status')
+ if state in _states_dict:
+ chunks = chunks.filter(_states_dict[state])
+
+ chunks = foreign_filter(chunks, arg_or_GET('user'), 'user', User, 'username')
+ chunks = foreign_filter(chunks, arg_or_GET('stage'), 'stage', Chunk.tag_model, 'slug')
+ chunks = search_filter(chunks, arg_or_GET('title'), ['book__title', 'title'])
+ chunks = foreign_filter(chunks, arg_or_GET('project'), 'book__project', Project, 'pk')
+ return chunks
+
+
+@register.inclusion_tag('catalogue/book_list/book_list.html', takes_context=True)
+def document_list(context, user=None, organization=None):
+ request = context['request']
+
+ #~ if user:
+ #~ filters = {"user": user}
+ #~ new_context = {"viewed_user": user}
+ #~ else:
+ #~ filters = {}
+ #~ new_context = {
+ #~ "users": User.objects.annotate(
+ #~ count=Count('chunk')).filter(count__gt=0).order_by(
+ #~ '-count', 'last_name', 'first_name'),
+ #~ "other_users": User.objects.annotate(
+ #~ count=Count('chunk')).filter(count=0).order_by(
+ #~ 'last_name', 'first_name'),
+ #~ }
+
+ docs = Document.objects.filter(deleted=False)
+ if user is not None:
+ docs = docs.filter(Q(owner_user=user) | Q(owner_organization__membership__user=user) | Q(assigned_to=user)).distinct()
+ if organization is not None:
+ docs = docs.filter(owner_organization=organization)
+
+ new_context = {}
+ new_context.update({
+ #"filters": True,
+ "request": request,
+ "books": docs,
+ #"books": ChunksList(document_list_filter(request, **filters)),
+ #"stages": Chunk.tag_model.objects.all(),
+ #"states": _states_options,
+ #"projects": Project.objects.all(),
+ })
+
+ return new_context
+
+
+@register.inclusion_tag('catalogue/book_list/book.html', takes_context=True)
+def document_short_html(context, doc):
+ user = context['request'].user
+ if doc.owner_organization is not None:
+ am_owner = doc.owner_organization.is_member(user)
+ else:
+ am_owner = doc.owner_user == user
+ return {
+ 'am_owner': am_owner,
+ 'book': doc,
+ }
--- /dev/null
+from django.utils import translation
+
+from django import template
+
+register = template.Library()
+
+
+@register.assignment_tag
+def flat_lang(page):
+ try:
+ return type(page).objects.get(url="%s%s/" % (page.url, translation.get_language()))
+ except:
+ return page
+
from django.conf.urls import patterns, url
from django.contrib.auth.decorators import permission_required, login_required
from django.views.generic import RedirectView
-from catalogue.feeds import PublishTrackFeed
from catalogue.views import GalleryView, GalleryPackageView
urlpatterns = patterns('catalogue.views',
- url(r'^$', RedirectView.as_view(url='catalogue/')),
+ #url(r'^$', RedirectView.as_view(url='catalogue/')),
- url(r'^catalogue/$', 'document_list', name='catalogue_document_list'),
+ url(r'^upcoming/$', 'upcoming', name='catalogue_upcoming'),
+ url(r'^finished/$', 'finished', name='catalogue_finished'),
+
+ #url(r'^catalogue/$', 'document_list', name='catalogue_document_list'),
url(r'^user/$', 'my', name='catalogue_user'),
url(r'^user/(?P<username>[^/]+)/$', 'user', name='catalogue_user'),
url(r'^users/$', 'users', name='catalogue_users'),
'create_missing', name='catalogue_create_missing'),
url(r'^create/',
'create_missing', name='catalogue_create_missing'),
+ url(r'^fork/(?P<pk>\d+)/',
+ 'fork', name='catalogue_fork'),
- url(r'^book/(?P<slug>[^/]+)/publish$', 'publish', name="catalogue_publish"),
+ url(r'^doc/(?P<pk>\d+)/publish$', 'publish', name="catalogue_publish"),
+ url(r'^doc/(?P<pk>\d+)/unpublish$', 'unpublish', name="catalogue_unpublish"),
#url(r'^(?P<name>[^/]+)/publish/(?P<version>\d+)$', 'publish', name="catalogue_publish"),
+ url(r'^(?P<pk>[^/]+)/schedule/$', 'book_schedule', name="catalogue_book_schedule"),
+ url(r'^(?P<pk>[^/]+)/owner/$', 'book_owner', name="catalogue_book_owner"),
+ url(r'^(?P<pk>[^/]+)/delete/$', 'book_delete', name="catalogue_book_delete"),
+
url(r'^book/(?P<slug>[^/]+)/$', 'book', name="catalogue_book"),
- url(r'^book/(?P<slug>[^/]+)/gallery/$',
+
+ url(r'^(?P<pk>[^/]+)/attachments/$',
login_required()(GalleryView.as_view()),
name="catalogue_book_gallery"),
+ #~ url(r'^attachments/$',
+ #~ login_required()(GalleryView.as_view()),
+ #~ name="catalogue_attachments"),
+ #~ url(r'^attachments/(?P<pk>\d+)/$',
+ #~ login_required()(GalleryView.as_view()),
+ #~ name="catalogue_attachments"),
url(r'^book/(?P<slug>[^/]+)/gallery/package$',
permission_required('catalogue.change_book')(GalleryPackageView.as_view()),
name="catalogue_book_gallery_package"),
url(r'^book/(?P<slug>[^/]+)/xml$', 'book_xml', name="catalogue_book_xml"),
- url(r'^book/(?P<slug>[^/]+)/txt$', 'book_txt', name="catalogue_book_txt"),
- url(r'^book/(?P<slug>[^/]+)/html$', 'book_html', name="catalogue_book_html"),
- url(r'^book/(?P<slug>[^/]+)/epub$', 'book_epub', name="catalogue_book_epub"),
- url(r'^book/(?P<slug>[^/]+)/pdf$', 'book_pdf', name="catalogue_book_pdf"),
+ #url(r'^book/(?P<slug>[^/]+)/txt$', 'book_txt', name="catalogue_book_txt"),
+ url(r'^(?P<pk>\d+)/$', 'book_html', name="catalogue_html"),
+ url(r'^(?P<pk>\d+)/preview/$', 'book_html', {'preview': True}, name="catalogue_preview"),
+ url(r'^(?P<pk>\d+)/rev(?P<rev_pk>\d+)/preview/$', 'book_html', {'preview': True}, name="catalogue_preview_rev"),
+ url(r'^(?P<pk>\d+)/rev(?P<rev_pk>\d+)/pdf/$', 'book_pdf', name="catalogue_pdf"),
+
+
+ #url(r'^book/(?P<slug>[^/]+)/epub$', 'book_epub', name="catalogue_book_epub"),
+ #url(r'^book/(?P<slug>[^/]+)/pdf$', 'book_pdf', name="catalogue_book_pdf"),
url(r'^chunk_add/(?P<slug>[^/]+)/(?P<chunk>[^/]+)/$',
'chunk_add', name="catalogue_chunk_add"),
'book_append', name="catalogue_book_append"),
url(r'^chunk_mass_edit',
'chunk_mass_edit', name='catalogue_chunk_mass_edit'),
-
- url(r'^track/(?P<slug>[^/]*)/$', PublishTrackFeed()),
)
from datetime import datetime, date, timedelta
import logging
import os
+import shutil
from StringIO import StringIO
from urllib import unquote
from urlparse import urlsplit, urlunsplit
from django.db import transaction
from django import http
from django.http import Http404, HttpResponse, HttpResponseForbidden
-from django.shortcuts import get_object_or_404, render, render_to_response
+from django.shortcuts import get_object_or_404, render, render_to_response, redirect
from django.utils.encoding import iri_to_uri
from django.utils.http import urlquote_plus
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.http import require_POST
from django.template import RequestContext
-from apiclient import NotAuthorizedError
from catalogue import forms
from catalogue import helpers
-from catalogue.helpers import active_tab
-from catalogue.models import Book, Chunk, BookPublishRecord, ChunkPublishRecord, Project
+from catalogue.helpers import active_tab, sstdocument
+from .constants import STAGES
+from .models import Document, Plan
+from dvcs.models import Revision
+from organizations.models import Organization
from fileupload.views import UploadView, PackageView
#
# Quick hack around caching problems, TODO: use ETags
#
from django.views.decorators.cache import never_cache
+#from fnpdjango.utils.text.slughifi import slughifi
logger = logging.getLogger("fnp.catalogue")
return http.HttpResponseRedirect(urlquote_plus(request.GET.get('next', '/'), safe='/?='))
-@permission_required('catalogue.add_book')
+#@permission_required('catalogue.add_book')
+@login_required
@active_tab('create')
-def create_missing(request, slug=None):
- if slug is None:
- slug = ''
- slug = slug.replace(' ', '-')
-
+def create_missing(request):
if request.method == "POST":
form = forms.DocumentCreateForm(request.POST, request.FILES)
if form.is_valid():
creator = request.user
else:
creator = None
- book = Book.create(
- text=form.cleaned_data['text'],
- creator=creator,
- slug=form.cleaned_data['slug'],
- title=form.cleaned_data['title'],
- gallery=form.cleaned_data['gallery'],
+
+ title = form.cleaned_data['title']
+ try:
+ org = request.user.membership_set.get(
+ organization=int(form.cleaned_data['owner_organization'])).organization
+ kwargs = {'owner_organization': org}
+ except:
+ kwargs = {'owner_user': request.user}
+
+ doc = Document.objects.create(**kwargs)
+
+ cover = request.FILES.get('cover')
+ if cover:
+ uppath = 'uploads/%d/' % doc.pk
+ path = settings.MEDIA_ROOT + uppath
+ if not os.path.isdir(path):
+ os.makedirs(path)
+ dest_path = path + cover.name # UNSAFE
+ with open(dest_path, 'w') as destination:
+ for chunk in cover.chunks():
+ destination.write(chunk)
+ cover_url = 'http://milpeer.eu/media/dynamic/' + uppath + cover.name
+ else:
+ cover_url = ''
+
+ doc.commit(
+ text = '''<section xmlns="http://nowoczesnapolska.org.pl/sst#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <metadata>
+ <dc:publisher>''' + form.cleaned_data['publisher'] + '''</dc:publisher>
+ <dc:description>''' + form.cleaned_data['description'] + '''</dc:description>
+ <dc:language>''' + form.cleaned_data['language'] + '''</dc:language>
+ <dc:rights>''' + form.cleaned_data['rights'] + '''</dc:rights>
+ <dc:audience>''' + form.cleaned_data['audience'] + '''</dc:audience>
+ <dc:relation.coverImage.url>''' + cover_url + '''</dc:relation.coverImage.url>
+ </metadata>
+ <header>''' + title + '''</header>
+ <div class="p"> </div>
+ </section>''',
+ author=creator
)
+ doc.assigned_to = request.user
+ doc.save()
- return http.HttpResponseRedirect(reverse("wiki_editor", args=[book.slug]))
+ return http.HttpResponseRedirect(reverse("wiki_editor", args=[doc.pk]))
else:
- form = forms.DocumentCreateForm(initial={
- "slug": slug,
- "title": slug.replace('-', ' ').title(),
- })
+ org_pk = request.GET.get('organization')
+ if org_pk:
+ try:
+ org = Organization.objects.get(pk=org_pk)
+ except Organization.DoesNotExist:
+ org = None
+ else:
+ if not org.is_member(request.user):
+ org = None
+ else:
+ org = None
+ if org is not None:
+ org = org.pk
+
+ form = forms.DocumentCreateForm(initial={'owner_organization': org})
return render(request, "catalogue/document_create_missing.html", {
- "slug": slug,
"form": form,
"logout_to": '/',
@never_cache
-def book_html(request, slug):
- book = get_object_or_404(Book, slug=slug)
- if not book.accessible(request):
- return HttpResponseForbidden("Not authorized.")
+def book_html(request, pk, rev_pk=None, preview=False):
+ from librarian.document import Document as SST
+ from librarian.formats.html import HtmlFormat
+
+ doc = get_object_or_404(Document, pk=pk, deleted=False)
+
+ try:
+ published_revision = doc.publish_log.all()[0].revision
+ except IndexError:
+ published_revision = None
- doc = book.wldocument(parse_dublincore=False)
- html = doc.as_html()
+ if rev_pk is None:
+ if preview:
+ revision = doc.revision
+ else:
+ if published_revision is not None:
+ revision = published_revision
+ else:
+ # No published version, fallback to preview mode.
+ preview = True
+ revision = doc.revision
+ else:
+ revision = get_object_or_404(Revision, pk=rev_pk)
+
+ was_published = revision == published_revision or doc.publish_log.filter(revision=revision).exists()
+
+ sst = SST.from_string(revision.materialize())
+ html = HtmlFormat(sst).build(files_path='http://%s/media/dynamic/uploads/%s/' % (request.get_host(), pk)).get_string()
- html = html.get_string() if html is not None else ''
# response = http.HttpResponse(html, content_type='text/html', mimetype='text/html')
# return response
# book_themes = {}
# book_themes = book_themes.items()
# book_themes.sort(key=lambda s: s[0].sort_key)
- return render_to_response('catalogue/book_text.html', locals(),
- context_instance=RequestContext(request))
+ return render(request, 'catalogue/book_text.html', {
+ 'doc': doc,
+ 'preview': preview,
+ 'revision': revision,
+ 'published_revision': published_revision,
+ 'specific': rev_pk is not None,
+ 'html': html,
+ 'can_edit': doc.can_edit(request.user) if doc else None,
+ 'was_published': was_published,
+ })
@never_cache
-def book_pdf(request, slug):
- book = get_object_or_404(Book, slug=slug)
- if not book.accessible(request):
- return HttpResponseForbidden("Not authorized.")
+def book_pdf(request, pk, rev_pk):
+ from librarian.utils import Context
+ from librarian.document import Document as SST
+ from librarian.formats.pdf import PdfFormat
+
+ doc = get_object_or_404(Document, pk=pk)
+ rev = get_object_or_404(Revision, pk=rev_pk)
+ # Test
+
+ sst = SST.from_string(rev.materialize())
+
+ ctx = Context(
+ files_path = 'http://%s/media/dynamic/uploads/%s/' % (request.get_host(), pk),
+ source_url = 'http://%s%s' % (request.get_host(), reverse('catalogue_html', args=[doc.pk])),
+ )
+ if doc.owner_organization is not None and doc.owner_organization.logo:
+ ctx.cover_logo = 'http://%s%s' %(request.get_host(), doc.owner_organization.logo.url)
+ pdf_file = PdfFormat(sst).build(ctx)
- # TODO: move to celery
- doc = book.wldocument()
- # TODO: error handling
- pdf_file = doc.as_pdf()
from catalogue.ebook_utils import serve_file
return serve_file(pdf_file.get_filename(),
- book.slug + '.pdf', 'application/pdf')
+ '%d.pdf' % doc.pk, 'application/pdf')
@never_cache
return http.HttpResponse(str(doc.revision()))
+@login_required
+def book_schedule(request, pk):
+ book = get_object_or_404(Document, pk=pk, deleted=False)
+ if request.method == 'POST':
+ Plan.objects.filter(document=book).delete()
+ for i, s in enumerate(STAGES):
+ user_id = request.POST.get('s%d-user' % i)
+ deadline = request.POST.get('s%d-deadline' % i) or None
+ Plan.objects.create(document=book, stage=s, user_id=user_id, deadline=deadline)
+
+ book.set_stage(request.POST.get('stage', ''))
+ return redirect('catalogue_user')
+
+ current = {}
+ for p in Plan.objects.filter(document=book):
+ current[p.stage] = (getattr(p.user, 'pk', None), (p.deadline.isoformat() if p.deadline else None))
+
+ schedule = [(i, s, current.get(s, ())) for (i, s) in enumerate(STAGES)]
+
+ if book.owner_organization:
+ people = [m.user for m in book.owner_organization.membership_set.exclude(status='pending')]
+ else:
+ people = [book.owner_user]
+ return render(request, 'catalogue/book_schedule.html', {
+ 'book': book,
+ 'schedule': schedule,
+ 'people': people,
+ })
+
+
+@login_required
+def book_owner(request, pk):
+ doc = get_object_or_404(Document, pk=pk, deleted=False)
+ if not (doc.owner_user == request.user or doc.owner_organization.is_member(request.user)):
+ raise Http404
+
+ error = ''
+
+ if request.method == 'POST':
+ # TODO: real form
+ new_org_pk = request.POST.get('owner_organization')
+ if not new_org_pk:
+ doc.owner_organization = None
+ doc.owner_user = request.user
+ doc.save()
+ else:
+ org = Organization.objects.get(pk=new_org_pk)
+ if not org.is_member(request.user):
+ error = 'Bad organization'
+ else:
+ doc.owner_organization = org
+ doc.owner_user = None
+ doc.save()
+ if not error:
+ return redirect('catalogue_user')
+
+ return render(request, 'catalogue/book_owner.html', {
+ 'doc': doc,
+ 'error': error,
+ })
+
+
+@login_required
+def book_delete(request, pk):
+ doc = get_object_or_404(Document, pk=pk, deleted=False)
+ if not (doc.owner_user == request.user or doc.owner_organization.is_member(request.user)):
+ raise Http404
+
+ if request.method == 'POST':
+ doc.deleted = True
+ doc.save()
+ return redirect('catalogue_user')
+
+ return render(request, 'catalogue/book_delete.html', {
+ 'doc': doc,
+ })
+
+
+
def book(request, slug):
book = get_object_or_404(Book, slug=slug)
if not book.accessible(request):
})
-@transaction.commit_on_success
+@transaction.atomic
@login_required
def chunk_mass_edit(request):
if request.method == 'POST':
@require_POST
@login_required
-def publish(request, slug):
- book = get_object_or_404(Book, slug=slug)
- if not book.accessible(request):
- return HttpResponseForbidden("Not authorized.")
+def publish(request, pk):
+ from wiki import forms
+ from .models import PublishRecord
+ from dvcs.models import Revision
+
+ # FIXME: check permissions
+
+ doc = get_object_or_404(Document, pk=pk, deleted=False)
+ form = forms.DocumentTextPublishForm(request.POST, prefix="textpublish")
+ if form.is_valid():
+ rev = Revision.objects.get(pk=form.cleaned_data['revision'])
+ # FIXME: check if in tree
+ #if PublishRecord.objects.filter(revision=rev, document=doc).exists():
+ # return http.HttpResponse('exists')
+ pr = PublishRecord.objects.create(revision=rev, document=doc, user=request.user)
+ if request.is_ajax():
+ return http.HttpResponse('ok')
+ else:
+ return redirect('catalogue_html', doc.pk)
+ else:
+ if request.is_ajax():
+ return http.HttpResponse('error')
+ else:
+ try:
+ return redirect('catalogue_preview_rev', doc.pk, form.cleaned_data['revision'])
+ except KeyError:
+ return redirect('catalogue_preview', doc.pk)
- try:
- book.publish(request.user)
- except NotAuthorizedError:
- return http.HttpResponseRedirect(reverse('apiclient_oauth'))
- except BaseException, e:
- return http.HttpResponse(e)
+
+@require_POST
+@login_required
+def unpublish(request, pk):
+ from wiki import forms
+ from .models import PublishRecord
+ from dvcs.models import Revision
+
+ # FIXME: check permissions
+
+ doc = get_object_or_404(Document, pk=pk, deleted=False)
+ doc.publish_log.all().delete()
+ if request.is_ajax():
+ return http.HttpResponse('ok')
else:
- return http.HttpResponseRedirect(book.get_absolute_url())
+ return redirect('catalogue_html', doc.pk)
+
class GalleryMixin(object):
def get_directory(self):
- return "%s%s/" % (settings.IMAGE_DIR, self.object.gallery)
- def get_object(self, request, slug):
- book = get_object_or_404(Book, slug=slug)
- if not book.gallery:
- raise Http404
- return book
+ #return "%s%s/" % (settings.IMAGE_DIR, 'org%d' % self.org.pk if self.org is not None else self.request.user.pk)
+ return "uploads/%d/" % (self.doc.pk)
+
class GalleryView(GalleryMixin, UploadView):
def breadcrumbs(self):
return [
- (u'moduły', reverse('catalogue_document_list')),
- (self.object.title, self.object.get_absolute_url()),
- (u'materiały',),
- ]
+ (self.doc.meta()['title'], '/documents/%d/' % self.doc.pk),
+ ]
+
+ def get_object(self, request, pk=None):
+ self.doc = Document.objects.get(pk=pk, deleted=False)
class GalleryPackageView(GalleryMixin, PackageView):
def get_redirect_url(self, slug):
return reverse('catalogue_book_gallery', kwargs = dict(slug=slug))
+
+@login_required
+def fork(request, pk):
+ doc = get_object_or_404(Document, pk=pk, deleted=False)
+ if request.method == "POST":
+ form = forms.DocumentForkForm(request.POST, request.FILES)
+ if form.is_valid():
+
+ if request.user.is_authenticated():
+ creator = request.user
+ else:
+ creator = None
+
+ try:
+ org = request.user.membership_set.get(
+ organization=int(form.cleaned_data['owner_organization'])).organization
+ kwargs = {'owner_organization': org}
+ except:
+ kwargs = {'owner_user': request.user}
+
+ new_doc = Document.objects.create(revision=doc.revision, **kwargs)
+
+ if os.path.isdir(settings.MEDIA_ROOT + "uploads/%d" % (doc.pk)):
+ shutil.copytree(
+ settings.MEDIA_ROOT + "uploads/%d" % (doc.pk),
+ settings.MEDIA_ROOT + "uploads/%d" % (new_doc.pk)
+ )
+
+ new_doc.assigned_to = request.user
+ new_doc.save()
+
+ return http.HttpResponseRedirect(reverse("wiki_editor", args=[new_doc.pk]))
+ else:
+ form = forms.DocumentForkForm()
+
+ return render(request, "catalogue/document_fork.html", {
+ "form": form,
+
+ "logout_to": '/',
+ })
+
+
+def upcoming(request):
+ return render(request, "catalogue/upcoming.html", {
+ 'objects_list': Document.objects.filter(deleted=False).filter(publish_log=None),
+ })
+
+def finished(request):
+ return render(request, "catalogue/finished.html", {
+ 'objects_list': Document.objects.filter(deleted=False).exclude(publish_log=None),
+ })
class ImageAddForm(forms.ModelForm):
class Meta:
model = Image
+ exclude = []
def __init__(self, *args, **kwargs):
super(ImageAddForm, self).__init__(*args, **kwargs)
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-06-18 12:36+0200\n"
+"POT-Creation-Date: 2016-01-26 10:36+0100\n"
"PO-Revision-Date: 2012-06-18 12:38+0100\n"
"Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
+"|| n%100>=20) ? 1 : 2)\n"
-#: forms.py:37
+#: forms.py:55
msgid "Flickr URL"
msgstr "URL z Flickra"
-#: models.py:18
+#: models.py:19
msgid "title"
msgstr "tytuł"
-#: models.py:19
+#: models.py:20
msgid "author"
msgstr "autor"
-#: models.py:20
+#: models.py:21
msgid "license name"
msgstr "nazwa licencji"
-#: models.py:21
+#: models.py:22
msgid "license URL"
msgstr "URL licencji"
-#: models.py:22
+#: models.py:23
msgid "source URL"
msgstr "URL źródła"
-#: models.py:23
+#: models.py:24
msgid "image download URL"
msgstr "URL pliku do pobrania"
-#: models.py:24
+#: models.py:25
msgid "file"
msgstr "plik"
-#: models.py:27
+#: models.py:28
msgid "cover image"
msgstr "obrazek na okładkę"
-#: models.py:28
+#: models.py:29
msgid "cover images"
msgstr "obrazki na okładki"
-#: templates/cover/add_image.html:6
-#: templates/cover/add_image.html.py:21
+#: templates/cover/add_image.html:33 templates/cover/add_image.html.py:62
msgid "Add image"
msgstr "Dodaj obrazek"
-#: templates/cover/add_image.html:13
+#: templates/cover/add_image.html:40
msgid "Load from Flickr"
msgstr "Pobierz z Flickra"
msgid "Change"
msgstr "Zmień"
-#: templates/cover/image_list.html:8
+#: templates/cover/image_detail.html:37
+msgid "Used in:"
+msgstr "Użyte w:"
+
+#: templates/cover/image_detail.html:45
+msgid "None"
+msgstr "Brak"
+
+#: templates/cover/image_list.html:7
msgid "Cover images"
msgstr "Obrazki na okładki"
-#: templates/cover/image_list.html:11
+#: templates/cover/image_list.html:10
msgid "Add new"
msgstr "Dodaj nowy"
-
+++ /dev/null
-# -*- 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 'Image'
- db.create_table('cover_image', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('title', self.gf('django.db.models.fields.CharField')(max_length=255)),
- ('author', self.gf('django.db.models.fields.CharField')(max_length=255)),
- ('license_name', self.gf('django.db.models.fields.CharField')(max_length=255)),
- ('license_url', self.gf('django.db.models.fields.URLField')(max_length=255, blank=True)),
- ('source_url', self.gf('django.db.models.fields.URLField')(max_length=200)),
- ('download_url', self.gf('django.db.models.fields.URLField')(unique=True, max_length=200)),
- ('file', self.gf('django.db.models.fields.files.ImageField')(max_length=100)),
- ))
- db.send_create_signal('cover', ['Image'])
-
-
- def backwards(self, orm):
- # Deleting model 'Image'
- db.delete_table('cover_image')
-
-
- models = {
- 'cover.image': {
- 'Meta': {'object_name': 'Image'},
- 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'download_url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'}),
- 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}),
- 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
- }
- }
-
- complete_apps = ['cover']
\ No newline at end of file
+++ /dev/null
-# -*- 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):
-
- # Changing field 'Image.download_url'
- db.alter_column(u'cover_image', 'download_url', self.gf('django.db.models.fields.URLField')(max_length=200, unique=True, null=True))
-
- def backwards(self, orm):
-
- # User chose to not deal with backwards NULL issues for 'Image.download_url'
- raise RuntimeError("Cannot reverse this migration. 'Image.download_url' and its values cannot be restored.")
-
- models = {
- u'cover.image': {
- 'Meta': {'object_name': 'Image'},
- 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'download_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'unique': 'True', 'null': 'True'}),
- 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}),
- 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
- }
- }
-
- complete_apps = ['cover']
\ No newline at end of file
+++ /dev/null
-# -*- 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):
-
- # Changing field 'Image.source_url'
- db.alter_column(u'cover_image', 'source_url', self.gf('django.db.models.fields.URLField')(max_length=200, null=True))
-
- def backwards(self, orm):
-
- # User chose to not deal with backwards NULL issues for 'Image.source_url'
- raise RuntimeError("Cannot reverse this migration. 'Image.source_url' and its values cannot be restored.")
-
- models = {
- u'cover.image': {
- 'Meta': {'object_name': 'Image'},
- 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'download_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'unique': 'True', 'null': 'True'}),
- 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}),
- 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
- 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
- }
- }
-
- complete_apps = ['cover']
\ No newline at end of file
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from catalogue.helpers import active_tab
-from catalogue.models import Chunk
+from catalogue.models import Document
from cover.models import Image
from cover import forms
+# -*- coding: utf-8 -*-
+# This file is part of django-dvcs, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See README.md for more information.
+#
+from .version import VERSION
+
+__version__ = VERSION
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import datetime
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Ref',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Revision',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('author_name', models.CharField(help_text='Used if author is not set.', max_length=128, null=True, verbose_name='author name', blank=True)),
+ ('author_email', models.CharField(help_text='Used if author is not set.', max_length=128, null=True, verbose_name='author email', blank=True)),
+ ('description', models.TextField(default='', verbose_name='description', blank=True)),
+ ('created_at', models.DateTimeField(default=datetime.datetime.now, editable=False, db_index=True)),
+ ('author', models.ForeignKey(verbose_name='author', blank=True, to=settings.AUTH_USER_MODEL, null=True)),
+ ('merge_parent', models.ForeignKey(related_name='merge_children', default=None, blank=True, to='dvcs.Revision', null=True, verbose_name='merge parent')),
+ ('parent', models.ForeignKey(related_name='children', default=None, blank=True, to='dvcs.Revision', null=True, verbose_name='parent')),
+ ],
+ options={
+ 'ordering': ('created_at',),
+ 'verbose_name': 'revision',
+ 'verbose_name_plural': 'revisions',
+ },
+ ),
+ migrations.AddField(
+ model_name='ref',
+ name='revision',
+ field=models.ForeignKey(default=None, editable=False, to='dvcs.Revision', blank=True, help_text="The document's revision.", null=True, verbose_name='revision'),
+ ),
+ ]
+from __future__ import unicode_literals, print_function
+
from datetime import datetime
-import os.path
+import os
+import re
+from subprocess import PIPE, Popen
+from tempfile import NamedTemporaryFile
-from django.contrib.auth.models import User
+from django.conf import settings
from django.core.files.base import ContentFile
from django.core.files.storage import FileSystemStorage
from django.db import models, transaction
from django.db.models.base import ModelBase
+from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
-from mercurial import simplemerge
-from django.conf import settings
-from dvcs.signals import post_commit, post_publishable
+from dvcs.signals import post_commit, post_merge
from dvcs.storage import GzipFileSystemStorage
+# default repository path; make a setting for it
+REPO_PATH = os.path.join(settings.MEDIA_ROOT, 'dvcs')
+repo = GzipFileSystemStorage(location=REPO_PATH)
-class Tag(models.Model):
- """A tag (e.g. document stage) which can be applied to a Change."""
- name = models.CharField(_('name'), max_length=64)
- slug = models.SlugField(_('slug'), unique=True, max_length=64,
- null=True, blank=True)
- ordering = models.IntegerField(_('ordering'))
-
- _object_cache = {}
-
- class Meta:
- abstract = True
- ordering = ['ordering']
- verbose_name = _("tag")
- verbose_name_plural = _("tags")
-
- def __unicode__(self):
- return self.name
-
- @classmethod
- def get(cls, slug):
- if slug in cls._object_cache:
- return cls._object_cache[slug]
- else:
- obj = cls.objects.get(slug=slug)
- cls._object_cache[slug] = obj
- return obj
-
- @staticmethod
- def listener_changed(sender, instance, **kwargs):
- sender._object_cache = {}
-
- def get_next(self):
- """
- Returns the next tag - stage to work on.
- Returns None for the last stage.
- """
- try:
- return type(self).objects.filter(ordering__gt=self.ordering)[0]
- except IndexError:
- return None
-
-models.signals.pre_save.connect(Tag.listener_changed, sender=Tag)
-
-
-def data_upload_to(instance, filename):
- return "%d/%d" % (instance.tree.pk, instance.pk)
-class Change(models.Model):
+@python_2_unicode_compatible
+class Revision(models.Model):
"""
- Single document change related to previous change. The "parent"
- argument points to the version against which this change has been
- recorded. Initial text will have a null parent.
-
- Data file contains a gzipped text of the document.
+ A document revision. The "parent"
+ argument points to the version against which this change has been
+ recorded. Initial text will have a null parent.
+
+ Gzipped text of the document is stored in a file.
"""
- author = models.ForeignKey(User, null=True, blank=True, verbose_name=_('author'))
+ author = models.ForeignKey(settings.AUTH_USER_MODEL,
+ null=True, blank=True, verbose_name=_('author'))
author_name = models.CharField(_('author name'), max_length=128,
null=True, blank=True,
help_text=_("Used if author is not set.")
null=True, blank=True,
help_text=_("Used if author is not set.")
)
- revision = models.IntegerField(_('revision'), db_index=True)
+ # Any other author data?
+ # How do we identify an author?
parent = models.ForeignKey('self',
null=True, blank=True, default=None,
description = models.TextField(_('description'), blank=True, default='')
created_at = models.DateTimeField(editable=False, db_index=True,
default=datetime.now)
- publishable = models.BooleanField(_('publishable'), default=False)
class Meta:
- abstract = True
ordering = ('created_at',)
- unique_together = ['tree', 'revision']
- verbose_name = _("change")
- verbose_name_plural = _("changes")
+ verbose_name = _("revision")
+ verbose_name_plural = _("revisions")
+
+ def __str__(self):
+ return "Id: %r, Parent %r, Data: %s" % (self.id, self.parent_id, self.get_text_path())
- def __unicode__(self):
- return u"Id: %r, Tree %r, Parent %r, Data: %s" % (self.id, self.tree_id, self.parent_id, self.data)
+ def get_text_path(self):
+ if self.pk:
+ return re.sub(r'([0-9a-f]{2})([^\.])', r'\1/\2', '%x.gz' % self.pk)
+ else:
+ return None
+
+ def save_text(self, content):
+ return repo.save(self.get_text_path(), ContentFile(content.encode('utf-8')))
def author_str(self):
if self.author:
self.author_email
)
-
- def save(self, *args, **kwargs):
- """
- take the next available revision number if none yet
- """
- if self.revision is None:
- tree_rev = self.tree.revision()
- if tree_rev is None:
- self.revision = 1
- else:
- self.revision = tree_rev + 1
- return super(Change, self).save(*args, **kwargs)
+ @classmethod
+ def create(cls, text, parent=None, merge_parent=None,
+ author=None, author_name=None, author_email=None,
+ description=''):
+
+ if text:
+ text = text.replace(
+ '<dc:></dc:>', '').replace(
+ '<div class="img">', '<div>')
+
+ revision = cls.objects.create(
+ parent=parent,
+ merge_parent=merge_parent,
+ author=author,
+ author_name=author_name,
+ author_email=author_email,
+ description=description
+ )
+ revision.save_text(text)
+ return revision
def materialize(self):
- f = self.data.storage.open(self.data)
- text = f.read()
+ f = repo.open(self.get_text_path())
+ text = f.read().decode('utf-8')
f.close()
- return unicode(text, 'utf-8')
-
- def merge_with(self, other, author=None,
- author_name=None, author_email=None,
- description=u"Automatic merge."):
- """Performs an automatic merge after straying commits."""
- assert self.tree_id == other.tree_id # same tree
- if other.parent_id == self.pk:
- # immediate child - fast forward
+ if text:
+ text = text.replace(
+ '<dc:></dc:>', '').replace(
+ '<div class="img">', '<div>')
+ return text
+
+ def is_descendant_of(self, other):
+ # Naive approach.
+ return (
+ (
+ self.parent is not None and (
+ self.parent.pk == other.pk or
+ self.parent.is_descendant_of(other)
+ )
+ ) or (
+ self.merge_parent is not None and (
+ self.merge_parent.pk == other.pk or
+ self.merge_parent.is_descendant_of(other)
+ )
+ )
+ )
+
+ def get_common_ancestor_with(self, other):
+ # VERY naive approach.
+ if self.pk == other.pk:
+ return self
+ if self.is_descendant_of(other):
return other
+ if other.is_descendant_of(self):
+ return self
- local = self.materialize().encode('utf-8')
- base = other.parent.materialize().encode('utf-8')
- remote = other.materialize().encode('utf-8')
-
- merge = simplemerge.Merge3Text(base, local, remote)
- result = ''.join(merge.merge_lines())
- merge_node = self.children.create(
- merge_parent=other, tree=self.tree,
- author=author,
- author_name=author_name,
- author_email=author_email,
- description=description)
- merge_node.data.save('', ContentFile(result))
- return merge_node
-
- def revert(self, **kwargs):
- """ commit this version of a doc as new head """
- self.tree.commit(text=self.materialize(), **kwargs)
-
- def set_publishable(self, publishable):
- self.publishable = publishable
- self.save()
- post_publishable.send(sender=self, publishable=publishable)
-
-
-def create_tag_model(model):
- name = model.__name__ + 'Tag'
-
- class Meta(Tag.Meta):
- app_label = model._meta.app_label
-
- if hasattr(model, 'TagMeta'):
- for attr, value in model.TagMeta.__dict__.items():
- setattr(Meta, attr, value)
-
- attrs = {
- '__module__': model.__module__,
- 'Meta': Meta,
- }
- return type(name, (Tag,), attrs)
-
-
-def create_change_model(model):
- name = model.__name__ + 'Change'
- repo = GzipFileSystemStorage(location=model.REPO_PATH)
-
- class Meta(Change.Meta):
- app_label = model._meta.app_label
-
- attrs = {
- '__module__': model.__module__,
- 'tree': models.ForeignKey(model, related_name='change_set', verbose_name=_('document')),
- 'tags': models.ManyToManyField(model.tag_model, verbose_name=_('tags'), related_name='change_set'),
- 'data': models.FileField(_('data'), upload_to=data_upload_to, storage=repo),
- 'Meta': Meta,
- }
- return type(name, (Change,), attrs)
-
-
-class DocumentMeta(ModelBase):
- "Metaclass for Document models."
- def __new__(cls, name, bases, attrs):
-
- model = super(DocumentMeta, cls).__new__(cls, name, bases, attrs)
- if not model._meta.abstract:
- # create a real Tag object and `stage' fk
- model.tag_model = create_tag_model(model)
- models.ForeignKey(model.tag_model, verbose_name=_('stage'),
- null=True, blank=True).contribute_to_class(model, 'stage')
-
- # create real Change model and `head' fk
- model.change_model = create_change_model(model)
-
- models.ForeignKey(model.change_model,
- null=True, blank=True, default=None,
- verbose_name=_('head'),
- help_text=_("This document's current head."),
- editable=False).contribute_to_class(model, 'head')
-
- models.ForeignKey(User, null=True, blank=True, editable=False,
- verbose_name=_('creator'), related_name="created_%s" % name.lower()
- ).contribute_to_class(model, 'creator')
-
- return model
-
-
-class Document(models.Model):
- """File in repository. Subclass it to use version control in your app."""
-
- __metaclass__ = DocumentMeta
-
- # default repository path
- REPO_PATH = os.path.join(settings.MEDIA_ROOT, 'dvcs')
-
- user = models.ForeignKey(User, null=True, blank=True,
- verbose_name=_('user'), help_text=_('Work assignment.'))
+ if self.parent is not None:
+ parent_ca = self.parent.get_common_ancestor_with(other)
+ else:
+ parent_ca = None
- class Meta:
- abstract = True
+ if self.merge_parent is not None:
+ merge_parent_ca = self.merge_parent.get_common_ancestor_with(other)
+ else:
+ return parent_ca
+
+ if parent_ca is None or parent_ca.created_at < merge_parent_ca.created_at:
+ return merge_parent_ca
+
+ return parent_ca
+
+ def get_ancestors(self):
+ revs = set()
+ if self.parent is not None:
+ revs.add(self.parent)
+ revs.update(self.parent.get_ancestors())
+ if self.merge_parent is not None:
+ revs.add(self.merge_parent)
+ revs.update(self.merge_parent.get_ancestors())
+ return revs
+
+@python_2_unicode_compatible
+class Ref(models.Model):
+ """A reference pointing to a specific revision."""
+
+ revision = models.ForeignKey(Revision,
+ null=True, blank=True, default=None,
+ verbose_name=_('revision'),
+ help_text=_("The document's revision."),
+ editable=False)
+
+ def __str__(self):
+ return "ref:{0}->rev:{1}".format(self.id, self.revision_id)
+
+ def merge_text(self, base, local, remote):
+ """Override in subclass to have different kinds of merges."""
+ files = []
+ for f in local, base, remote:
+ temp = NamedTemporaryFile(delete=False)
+ temp.write(f)
+ temp.close()
+ files.append(temp.name)
+ p = Popen(['/usr/bin/diff3', '-mE', '-L', 'old', '-L', '', '-L', 'new'] + files, stdout=PIPE)
+ result, errs = p.communicate()
+
+ for f in files:
+ os.unlink(f)
+
+ return result.decode('utf-8')
- def __unicode__(self):
- return u"{0}, HEAD: {1}".format(self.id, self.head_id)
+ def merge_with(self, revision,
+ author=None, author_name=None, author_email=None,
+ description="Automatic merge."):
+ """Merges a given revision into the ref."""
+ if self.revision is None:
+ fast_forward = True
+ self.revision = revision
+ elif self.revision.pk == revision.pk or self.revision.is_descendant_of(revision):
+ # Already merged.
+ return
+ elif revision.is_descendant_of(self.revision):
+ # Fast forward.
+ fast_forward = True
+ self.revision = revision
+ else:
+ # Need to create a merge revision.
+ fast_forward = False
+ base = self.revision.get_common_ancestor_with(revision)
+
+ local_text = self.materialize().encode('utf-8')
+ base_text = base.materialize().encode('utf-8')
+ other_text = revision.materialize().encode('utf-8')
+
+ merge_text = self.merge_text(base_text, local_text, other_text)
+
+ merge_revision = Revision.create(
+ text=merge_text,
+ parent=self.revision,
+ merge_parent=revision,
+ author=author,
+ author_name=author_name,
+ author_email=author_email,
+ description=description
+ )
+ self.revision = merge_revision
+ self.save()
+ post_merge.send(sender=type(self), instance=self, fast_forward=fast_forward)
- def materialize(self, change=None):
- if self.head is None:
- return u''
- if change is None:
- change = self.head
- elif not isinstance(change, Change):
- change = self.change_set.get(pk=change)
- return change.materialize()
+ def materialize(self):
+ return self.revision.materialize() if self.revision is not None else ''
- def commit(self, text, author=None, author_name=None, author_email=None,
- publishable=False, **kwargs):
- """Commits a new revision.
+ def commit(self, text, parent=False,
+ author=None, author_name=None, author_email=None,
+ description=''):
+ """Creates a new revision and sets it as the ref.
This will automatically merge the commit into the main branch,
if parent is not document's head.
:param unicode text: new version of the document
- :param parent: parent revision (head, if not specified)
- :type parent: Change or None
+ :param base: parent revision (head, if not specified)
+ :type base: Revision or None
:param User author: the commiter
:param unicode author_name: commiter name (if ``author`` not specified)
:param unicode author_email: commiter e-mail (if ``author`` not specified)
- :param Tag[] tags: list of tags to apply to the new commit
- :param bool publishable: set new commit as ready to publish
:returns: new head
"""
- if 'parent' not in kwargs:
- parent = self.head
- else:
- parent = kwargs['parent']
- if parent is not None and not isinstance(parent, Change):
- parent = self.change_set.objects.get(pk=kwargs['parent'])
-
- tags = kwargs.get('tags', [])
- if tags:
- # set stage to next tag after the commited one
- self.stage = max(tags, key=lambda t: t.ordering).get_next()
-
- change = self.change_set.create(author=author,
- author_name=author_name,
- author_email=author_email,
- description=kwargs.get('description', ''),
- publishable=publishable,
- parent=parent)
-
- change.tags = tags
- change.data.save('', ContentFile(text.encode('utf-8')))
- change.save()
-
- if self.head:
- # merge new change as new head
- self.head = self.head.merge_with(change, author=author,
- author_name=author_name,
- author_email=author_email)
- else:
- self.head = change
- self.save()
-
- post_commit.send(sender=self.head)
-
- return self.head
+ if parent is False:
+ # If parent revision not set explicitly, use your head.
+ parent = self.revision
+
+ # Warning: this will silently leave revs unreferenced.
+ rev = Revision.create(
+ text=text,
+ author=author,
+ author_name=author_name,
+ author_email=author_email,
+ description=description,
+ parent=parent
+ )
+ self.merge_with(rev, author=author, author_name=author_name,
+ author_email=author_email)
+
+ post_commit.send(sender=type(self), instance=self)
def history(self):
- return self.change_set.all().order_by('revision')
-
- def revision(self):
- rev = self.change_set.aggregate(
- models.Max('revision'))['revision__max']
- return rev
-
- def at_revision(self, rev):
- """Returns a Change with given revision number."""
- return self.change_set.get(revision=rev)
-
- def publishable(self):
- changes = self.history().filter(publishable=True)
- if changes.exists():
- return changes.order_by('-revision')[0]
- else:
- return None
-
- @transaction.commit_on_success
- def prepend_history(self, other):
- """Takes over the the other document's history and prepends to own."""
-
- assert self != other
- other_revs = other.change_set.all().count()
- # workaround for a non-atomic UPDATE in SQLITE
- self.change_set.all().update(revision=0-models.F('revision'))
- self.change_set.all().update(revision=other_revs - models.F('revision'))
- other.change_set.all().update(tree=self)
- assert not other.change_set.exists()
- other.delete()
+ revs = self.revision.get_ancestors()
+ revs.add(self.revision)
+ return sorted(revs, key=lambda x: x.created_at)
+from __future__ import unicode_literals
+
from django.dispatch import Signal
-post_commit = Signal()
-post_publishable = Signal(providing_args=['publishable'])
+post_commit = Signal(providing_args=['instance'])
+post_merge = Signal(providing_args=['fast_forward', 'instance'])
\ No newline at end of file
-from zlib import compress, decompress
+from __future__ import unicode_literals
+import os
from django.core.files.base import ContentFile, File
from django.core.files.storage import FileSystemStorage
+try:
+ from gzip import compress, decompress
+except ImportError:
+ # Python < 3.2
+ from gzip import GzipFile
+ from StringIO import StringIO
+
+ def compress(data):
+ compressed = StringIO()
+ GzipFile(fileobj=compressed, mode="wb").write(data)
+ return compressed.getvalue()
+
+ def decompress(data):
+ return GzipFile(fileobj=StringIO(data)).read()
+
class GzipFileSystemStorage(FileSystemStorage):
def _open(self, name, mode='rb'):
def _save(self, name, content):
content = ContentFile(compress(content.read()))
-
return super(GzipFileSystemStorage, self)._save(name, content)
+
+ def get_available_name(self, name):
+ if self.exists(name):
+ self.delete(name)
+ return name
\ No newline at end of file
--- /dev/null
+from __future__ import unicode_literals
+
+VERSION='0.1'
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-12-19 12:38+0100\n"
+"POT-Creation-Date: 2016-01-26 10:37+0100\n"
"PO-Revision-Date: 2013-03-07 16:27+0100\n"
"Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2);\n"
-#: templates/fileupload/picture_form.html:18
-msgid "Browse:"
-msgstr "Przeglądanie:"
+#: templates/fileupload/picture_form.html:16
+msgid "Attachments for:"
+msgstr "Załączniki dla:"
-#: templates/fileupload/picture_form.html:34
+#: templates/fileupload/picture_form.html:33
msgid "Add files..."
msgstr "Dodaj pliki..."
-#: templates/fileupload/picture_form.html:39
+#: templates/fileupload/picture_form.html:38
msgid "Start upload"
msgstr "Zacznij wysyłać"
-#: templates/fileupload/picture_form.html:43
+#: templates/fileupload/picture_form.html:42
msgid "Cancel upload"
msgstr "Anuluj wysyłanie"
-#: templates/fileupload/picture_form.html:47
+#: templates/fileupload/picture_form.html:46
msgid "Delete"
msgstr "Usuń"
-#: templates/fileupload/picture_form.html:51
+#: templates/fileupload/picture_form.html:50
msgid "Create package"
msgstr "Stwórz paczkę"
+
+#~ msgid "Browse:"
+#~ msgstr "Przeglądanie:"
{% load upload_tags %}
{% block add_css %}
- <link rel="stylesheet" href="{{ STATIC_URL }}fileupload/css/bootstrap.min.css">
+ <!--link rel="stylesheet" href="{{ STATIC_URL }}fileupload/css/bootstrap.min.css">
<link rel="stylesheet" href="{{ STATIC_URL }}fileupload/css/style.css">
- <link rel="stylesheet" href="{{ STATIC_URL }}fileupload/css/bootstrap-image-gallery.min.css">
+ <link rel="stylesheet" href="{{ STATIC_URL }}fileupload/css/bootstrap-image-gallery.min.css"-->
<link rel="stylesheet" href="{{ STATIC_URL }}fileupload/css/jquery.fileupload-ui.css">
- <!-- Shim to make HTML5 elements usable in older Internet Explorer versions -->
- <!--[if lt IE 9]><script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
{% endblock %}
-{% block content %}
+{% block inner_content %}
-<h1>
-{% trans "Browse:" %}
+<h1 style="margin-bottom: 1em">
+{% trans "Attachments for:" %}
{% for crumb in view.breadcrumbs %}
{% if crumb.1 %}
<a href="{{ crumb.1 }}">{{ crumb.0 }}</a>
<i class="icon-trash icon-white"></i>
<span>{% trans "Delete" %}</span>
</button>
- <a class="btn btn-info" href="package">
+ <!--a class="btn btn-info" href="package">
<i class="icon-gift icon-white"></i>
<span>{% trans "Create package" %}</span>
- </a>
+ </a-->
<input type="checkbox" class="toggle">
</div>
<div class="span5 fileupload-progress fade">
{% block add_js %}
{% upload_js %}
-<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
+<!--script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script-->
<script src="{{ STATIC_URL }}fileupload/js/jquery.ui.widget.js"></script>
<script src="{{ STATIC_URL }}fileupload/js/tmpl.min.js"></script>
<script src="{{ STATIC_URL }}fileupload/js/load-image.min.js"></script>
<script src="{{ STATIC_URL }}fileupload/js/canvas-to-blob.min.js"></script>
-<script src="{{ STATIC_URL }}fileupload/js/bootstrap.min.js"></script>
+<!--script src="{{ STATIC_URL }}fileupload/js/bootstrap.min.js"></script-->
<script src="{{ STATIC_URL }}fileupload/js/bootstrap-image-gallery.min.js"></script>
<script src="{{ STATIC_URL }}fileupload/js/jquery.iframe-transport.js"></script>
<script src="{{ STATIC_URL }}fileupload/js/jquery.fileupload.js"></script>
--- /dev/null
+__version__ = "0.12.2"
--- /dev/null
+#!/usr/bin/env python
+from __future__ import absolute_import, unicode_literals
+
+import sys
+import os
+
+from settings import PROJECT_ROOT, PROJECT_DIRNAME
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..'))
+sys.path.insert(0, os.path.abspath(os.path.join(PROJECT_ROOT, "..")))
+
+if __name__ == "__main__":
+ settings_module = "%s.settings" % PROJECT_DIRNAME
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings_module)
+ from django.core.management import execute_from_command_line
+ execute_from_command_line(sys.argv)
--- /dev/null
+from __future__ import absolute_import, unicode_literals
+
+import os, sys
+
+
+DEBUG = True
+SITE_ID = 1
+PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
+PROJECT_DIRNAME = PROJECT_ROOT.split(os.sep)[-1]
+STATIC_URL = "/static/"
+STATIC_ROOT = os.path.join(PROJECT_ROOT, STATIC_URL.strip("/"))
+MEDIA_URL = STATIC_URL + "media/"
+MEDIA_ROOT = os.path.join(PROJECT_ROOT, *MEDIA_URL.strip("/").split("/"))
+ADMIN_MEDIA_PREFIX = STATIC_URL + "admin/"
+ROOT_URLCONF = "%s.urls" % PROJECT_DIRNAME
+TEMPLATE_DIRS = (os.path.join(PROJECT_ROOT, "templates"),)
+SECRET_KEY = "asdfa4wtW#$Gse4aGdfs"
+ADMINS = ()
+
+
+MANAGERS = ADMINS
+if "test" not in sys.argv:
+ LOGIN_URL = "/admin/"
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': 'dev.db',
+ }
+}
+
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+ "django.contrib.auth.context_processors.auth",
+ "django.contrib.messages.context_processors.messages",
+ "django.core.context_processors.debug",
+ "django.core.context_processors.i18n",
+ "django.core.context_processors.static",
+ "django.core.context_processors.media",
+ "django.core.context_processors.request",
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.staticfiles',
+ 'forms_builder.forms',
+)
+
+from django import VERSION
+if VERSION < (1, 7):
+ try:
+ import south
+ except ImportError:
+ pass
+ else:
+ INSTALLED_APPS += ("south",)
+
+FORMS_BUILDER_EXTRA_FIELDS = (
+ (100, "django.forms.BooleanField", "My cool checkbox"),
+)
+
+try:
+ from local_settings import *
+except ImportError:
+ pass
+
+TEMPLATE_DEBUG = DEBUG
--- /dev/null
+<!doctype html>
+<head>
+ <meta charset="utf-8">
+ <title>{{ form.title }}</title>
+ <style>
+ body {font-family:sans-serif; padding:1em 2em;}
+ p {width:50em;}
+ label {display:block; float:left; width:10em;}
+ .errorlist {color:#f00;}
+ </style>
+</head>
+<body>
+ <h1>Forms</h1>
+ {% for form in forms %}
+ <p><a href="{{ form.get_absolute_url }}">{{ form.title }}</a></p>
+ {% empty %}
+ <p>No forms created. Go to the <a href="/admin/">admin</a> to create a form.</p>
+ {% endfor %}
+</body>
+</html>
+
--- /dev/null
+from __future__ import unicode_literals
+
+from django.conf.urls import patterns, include, url
+from django.contrib import admin
+from django.shortcuts import render
+
+from forms_builder.forms.models import Form
+from forms_builder.forms import urls as form_urls
+
+
+admin.autodiscover()
+
+urlpatterns = patterns('',
+ url(r'^admin/', include(admin.site.urls)),
+ url(r'^forms/', include(form_urls)),
+ url(r'^$', lambda request: render(request, "index.html",
+ {"forms": Form.objects.all()})),
+)
--- /dev/null
+from __future__ import unicode_literals
+from future.builtins import bytes, open
+
+from csv import writer
+from mimetypes import guess_type
+from os.path import join
+from datetime import datetime
+from io import BytesIO, StringIO
+
+from django.conf.urls import patterns, url
+from django.contrib import admin
+from django.core.files.storage import FileSystemStorage
+from django.core.urlresolvers import reverse
+from django.db.models import Count
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render_to_response, get_object_or_404
+from django.template import RequestContext
+from django.utils.translation import ungettext, ugettext_lazy as _
+
+from forms_builder.forms.forms import EntriesForm
+from forms_builder.forms.models import Form, Field, FormEntry, FieldEntry
+from forms_builder.forms.settings import CSV_DELIMITER, UPLOAD_ROOT
+from forms_builder.forms.settings import USE_SITES, EDITABLE_SLUGS
+from forms_builder.forms.utils import now, slugify
+
+try:
+ import xlwt
+ XLWT_INSTALLED = True
+ XLWT_DATETIME_STYLE = xlwt.easyxf(num_format_str='MM/DD/YYYY HH:MM:SS')
+except ImportError:
+ XLWT_INSTALLED = False
+
+
+fs = FileSystemStorage(location=UPLOAD_ROOT)
+form_admin_filter_horizontal = ()
+form_admin_fieldsets = [
+ (None, {"fields": ("title", ("status", "login_required",),
+ ("publish_date", "expiry_date",),
+ "intro", "button_text", "response", "redirect_url")}),
+ (_("Email"), {"fields": ("send_email", "email_from", "email_copies",
+ "email_subject", "email_message")}),]
+
+if EDITABLE_SLUGS:
+ form_admin_fieldsets.append(
+ (_("Slug"), {"fields": ("slug",), "classes": ("collapse",)}))
+
+if USE_SITES:
+ form_admin_fieldsets.append((_("Sites"), {"fields": ("sites",),
+ "classes": ("collapse",)}))
+ form_admin_filter_horizontal = ("sites",)
+
+
+class FieldAdmin(admin.TabularInline):
+ model = Field
+ exclude = ('slug', )
+
+
+class FormAdmin(admin.ModelAdmin):
+ formentry_model = FormEntry
+ fieldentry_model = FieldEntry
+
+ inlines = (FieldAdmin,)
+ list_display = ("title", "status", "email_copies", "publish_date",
+ "expiry_date", "total_entries", "admin_links")
+ list_display_links = ("title",)
+ list_editable = ("status", "email_copies", "publish_date", "expiry_date")
+ list_filter = ("status",)
+ filter_horizontal = form_admin_filter_horizontal
+ search_fields = ("title", "intro", "response", "email_from",
+ "email_copies")
+ radio_fields = {"status": admin.HORIZONTAL}
+ fieldsets = form_admin_fieldsets
+
+ def get_queryset(self, request):
+ """
+ Annotate the queryset with the entries count for use in the
+ admin list view.
+ """
+ qs = super(FormAdmin, self).get_queryset(request)
+ return qs.annotate(total_entries=Count("entries"))
+
+ def get_urls(self):
+ """
+ Add the entries view to urls.
+ """
+ urls = super(FormAdmin, self).get_urls()
+ extra_urls = patterns("",
+ url("^(?P<form_id>\d+)/entries/$",
+ self.admin_site.admin_view(self.entries_view),
+ name="form_entries"),
+ url("^(?P<form_id>\d+)/entries/show/$",
+ self.admin_site.admin_view(self.entries_view),
+ {"show": True}, name="form_entries_show"),
+ url("^(?P<form_id>\d+)/entries/export/$",
+ self.admin_site.admin_view(self.entries_view),
+ {"export": True}, name="form_entries_export"),
+ url("^file/(?P<field_entry_id>\d+)/$",
+ self.admin_site.admin_view(self.file_view),
+ name="form_file"),
+ )
+ return extra_urls + urls
+
+ def entries_view(self, request, form_id, show=False, export=False,
+ export_xls=False):
+ """
+ Displays the form entries in a HTML table with option to
+ export as CSV file.
+ """
+ if request.POST.get("back"):
+ bits = (self.model._meta.app_label, self.model.__name__.lower())
+ change_url = reverse("admin:%s_%s_change" % bits, args=(form_id,))
+ return HttpResponseRedirect(change_url)
+ form = get_object_or_404(self.model, id=form_id)
+ post = request.POST or None
+ args = form, request, self.formentry_model, self.fieldentry_model, post
+ entries_form = EntriesForm(*args)
+ delete = "%s.delete_formentry" % self.formentry_model._meta.app_label
+ can_delete_entries = request.user.has_perm(delete)
+ submitted = entries_form.is_valid() or show or export or export_xls
+ export = export or request.POST.get("export")
+ export_xls = export_xls or request.POST.get("export_xls")
+ if submitted:
+ if export:
+ response = HttpResponse(content_type="text/csv")
+ fname = "%s-%s.csv" % (form.slug, slugify(now().ctime()))
+ attachment = "attachment; filename=%s" % fname
+ response["Content-Disposition"] = attachment
+ queue = StringIO()
+ try:
+ csv = writer(queue, delimiter=CSV_DELIMITER)
+ writerow = csv.writerow
+ except TypeError:
+ queue = BytesIO()
+ delimiter = bytes(CSV_DELIMITER, encoding="utf-8")
+ csv = writer(queue, delimiter=delimiter)
+ writerow = lambda row: csv.writerow([c.encode("utf-8")
+ if hasattr(c, "encode") else c for c in row])
+ writerow(entries_form.columns())
+ for row in entries_form.rows(csv=True):
+ writerow(row)
+ data = queue.getvalue()
+ response.write(data)
+ return response
+ elif XLWT_INSTALLED and export_xls:
+ response = HttpResponse(content_type="application/vnd.ms-excel")
+ fname = "%s-%s.xls" % (form.slug, slugify(now().ctime()))
+ attachment = "attachment; filename=%s" % fname
+ response["Content-Disposition"] = attachment
+ queue = BytesIO()
+ workbook = xlwt.Workbook(encoding='utf8')
+ sheet = workbook.add_sheet(form.title[:31])
+ for c, col in enumerate(entries_form.columns()):
+ sheet.write(0, c, col)
+ for r, row in enumerate(entries_form.rows(csv=True)):
+ for c, item in enumerate(row):
+ if isinstance(item, datetime):
+ item = item.replace(tzinfo=None)
+ sheet.write(r + 2, c, item, XLWT_DATETIME_STYLE)
+ else:
+ sheet.write(r + 2, c, item)
+ workbook.save(queue)
+ data = queue.getvalue()
+ response.write(data)
+ return response
+ elif request.POST.get("delete") and can_delete_entries:
+ selected = request.POST.getlist("selected")
+ if selected:
+ try:
+ from django.contrib.messages import info
+ except ImportError:
+ def info(request, message, fail_silently=True):
+ request.user.message_set.create(message=message)
+ entries = self.formentry_model.objects.filter(id__in=selected)
+ count = entries.count()
+ if count > 0:
+ entries.delete()
+ message = ungettext("1 entry deleted",
+ "%(count)s entries deleted", count)
+ info(request, message % {"count": count})
+ template = "admin/forms/entries.html"
+ context = {"title": _("View Entries"), "entries_form": entries_form,
+ "opts": self.model._meta, "original": form,
+ "can_delete_entries": can_delete_entries,
+ "submitted": submitted,
+ "xlwt_installed": XLWT_INSTALLED}
+ return render_to_response(template, context, RequestContext(request))
+
+ def file_view(self, request, field_entry_id):
+ """
+ Output the file for the requested field entry.
+ """
+ model = self.fieldentry_model
+ field_entry = get_object_or_404(model, id=field_entry_id)
+ path = join(fs.location, field_entry.value)
+ response = HttpResponse(content_type=guess_type(path)[0])
+ f = open(path, "r+b")
+ response["Content-Disposition"] = "attachment; filename=%s" % f.name
+ response.write(f.read())
+ f.close()
+ return response
+
+
+admin.site.register(Form, FormAdmin)
--- /dev/null
+from __future__ import unicode_literals
+
+from django.core.exceptions import ImproperlyConfigured
+from django import forms
+from django.forms.extras import SelectDateWidget
+from django.utils.translation import ugettext_lazy as _
+
+from forms_builder.forms.settings import USE_HTML5, EXTRA_FIELDS, EXTRA_WIDGETS
+from forms_builder.forms.utils import html5_field, import_attr
+
+
+# Constants for all available field types.
+TEXT = 1
+TEXTAREA = 2
+EMAIL = 3
+CHECKBOX = 4
+CHECKBOX_MULTIPLE = 5
+SELECT = 6
+SELECT_MULTIPLE = 7
+RADIO_MULTIPLE = 8
+FILE = 9
+DATE = 10
+DATE_TIME = 11
+HIDDEN = 12
+NUMBER = 13
+URL = 14
+DOB = 15
+
+# Names for all available field types.
+NAMES = (
+ (TEXT, _("Single line text")),
+ (TEXTAREA, _("Multi line text")),
+ (EMAIL, _("Email")),
+ (NUMBER, _("Number")),
+ (URL, _("URL")),
+ (CHECKBOX, _("Check box")),
+ (CHECKBOX_MULTIPLE, _("Check boxes")),
+ (SELECT, _("Drop down")),
+ (SELECT_MULTIPLE, _("Multi select")),
+ (RADIO_MULTIPLE, _("Radio buttons")),
+ (FILE, _("File upload")),
+ (DATE, _("Date")),
+ (DATE_TIME, _("Date/time")),
+ (DOB, _("Date of birth")),
+ (HIDDEN, _("Hidden")),
+)
+
+# Field classes for all available field types.
+CLASSES = {
+ TEXT: forms.CharField,
+ TEXTAREA: forms.CharField,
+ EMAIL: forms.EmailField,
+ CHECKBOX: forms.BooleanField,
+ CHECKBOX_MULTIPLE: forms.MultipleChoiceField,
+ SELECT: forms.ChoiceField,
+ SELECT_MULTIPLE: forms.MultipleChoiceField,
+ RADIO_MULTIPLE: forms.ChoiceField,
+ FILE: forms.FileField,
+ DATE: forms.DateField,
+ DATE_TIME: forms.DateTimeField,
+ DOB: forms.DateField,
+ HIDDEN: forms.CharField,
+ NUMBER: forms.FloatField,
+ URL: forms.URLField,
+}
+
+# Widgets for field types where a specialised widget is required.
+WIDGETS = {
+ TEXTAREA: forms.Textarea,
+ CHECKBOX_MULTIPLE: forms.CheckboxSelectMultiple,
+ RADIO_MULTIPLE: forms.RadioSelect,
+ DATE: SelectDateWidget,
+ DOB: SelectDateWidget,
+ HIDDEN: forms.HiddenInput,
+}
+
+# Some helper groupings of field types.
+CHOICES = (CHECKBOX, SELECT, RADIO_MULTIPLE)
+DATES = (DATE, DATE_TIME, DOB)
+MULTIPLE = (CHECKBOX_MULTIPLE, SELECT_MULTIPLE)
+
+# HTML5 Widgets
+if USE_HTML5:
+ WIDGETS.update({
+ DATE: html5_field("date", forms.DateInput),
+ DATE_TIME: html5_field("datetime", forms.DateTimeInput),
+ DOB: html5_field("date", forms.DateInput),
+ EMAIL: html5_field("email", forms.TextInput),
+ NUMBER: html5_field("number", forms.TextInput),
+ URL: html5_field("url", forms.TextInput),
+ })
+
+# Add any custom fields defined.
+for field_id, field_path, field_name in EXTRA_FIELDS:
+ if field_id in CLASSES:
+ err = "ID %s for field %s in FORMS_EXTRA_FIELDS already exists"
+ raise ImproperlyConfigured(err % (field_id, field_name))
+ CLASSES[field_id] = import_attr(field_path)
+ NAMES += ((field_id, _(field_name)),)
+
+# Add/update custom widgets.
+for field_id, widget_path in EXTRA_WIDGETS:
+ if field_id not in CLASSES:
+ err = "ID %s in FORMS_EXTRA_WIDGETS does not match a field"
+ raise ImproperlyConfigured(err % field_id)
+ WIDGETS[field_id] = import_attr(widget_path)
--- /dev/null
+from __future__ import unicode_literals
+from future.builtins import int, range, str
+
+from datetime import date, datetime
+from os.path import join, split
+from uuid import uuid4
+
+import django
+from django import forms
+from django.forms.extras import SelectDateWidget
+from django.core.files.storage import FileSystemStorage
+from django.core.urlresolvers import reverse
+from django.template import Template
+from django.utils.safestring import mark_safe
+from django.utils.translation import ugettext_lazy as _
+
+from forms_builder.forms import fields
+from forms_builder.forms.models import FormEntry, FieldEntry
+from forms_builder.forms import settings
+from forms_builder.forms.utils import now, split_choices
+
+
+fs = FileSystemStorage(location=settings.UPLOAD_ROOT)
+
+##############################
+# Each type of export filter #
+##############################
+
+# Text matches
+FILTER_CHOICE_CONTAINS = "1"
+FILTER_CHOICE_DOESNT_CONTAIN = "2"
+
+# Exact matches
+FILTER_CHOICE_EQUALS = "3"
+FILTER_CHOICE_DOESNT_EQUAL = "4"
+
+# Greater/less than
+FILTER_CHOICE_BETWEEN = "5"
+
+# Multiple values
+FILTER_CHOICE_CONTAINS_ANY = "6"
+FILTER_CHOICE_CONTAINS_ALL = "7"
+FILTER_CHOICE_DOESNT_CONTAIN_ANY = "8"
+FILTER_CHOICE_DOESNT_CONTAIN_ALL = "9"
+
+##########################
+# Export filters grouped #
+##########################
+
+# Text fields
+TEXT_FILTER_CHOICES = (
+ ("", _("Nothing")),
+ (FILTER_CHOICE_CONTAINS, _("Contains")),
+ (FILTER_CHOICE_DOESNT_CONTAIN, _("Doesn't contain")),
+ (FILTER_CHOICE_EQUALS, _("Equals")),
+ (FILTER_CHOICE_DOESNT_EQUAL, _("Doesn't equal")),
+)
+
+# Choices with single value entries
+CHOICE_FILTER_CHOICES = (
+ ("", _("Nothing")),
+ (FILTER_CHOICE_CONTAINS_ANY, _("Equals any")),
+ (FILTER_CHOICE_DOESNT_CONTAIN_ANY, _("Doesn't equal any")),
+)
+
+# Choices with multiple value entries
+MULTIPLE_FILTER_CHOICES = (
+ ("", _("Nothing")),
+ (FILTER_CHOICE_CONTAINS_ANY, _("Contains any")),
+ (FILTER_CHOICE_CONTAINS_ALL, _("Contains all")),
+ (FILTER_CHOICE_DOESNT_CONTAIN_ANY, _("Doesn't contain any")),
+ (FILTER_CHOICE_DOESNT_CONTAIN_ALL, _("Doesn't contain all")),
+)
+
+# Dates
+DATE_FILTER_CHOICES = (
+ ("", _("Nothing")),
+ (FILTER_CHOICE_BETWEEN, _("Is between")),
+)
+
+# The filter function for each filter type
+FILTER_FUNCS = {
+ FILTER_CHOICE_CONTAINS:
+ lambda val, field: val.lower() in field.lower(),
+ FILTER_CHOICE_DOESNT_CONTAIN:
+ lambda val, field: val.lower() not in field.lower(),
+ FILTER_CHOICE_EQUALS:
+ lambda val, field: val.lower() == field.lower(),
+ FILTER_CHOICE_DOESNT_EQUAL:
+ lambda val, field: val.lower() != field.lower(),
+ FILTER_CHOICE_BETWEEN:
+ lambda val_from, val_to, field: (
+ (not val_from or val_from <= field) and
+ (not val_to or val_to >= field)
+ ),
+ FILTER_CHOICE_CONTAINS_ANY:
+ lambda val, field: set(val) & set(split_choices(field)),
+ FILTER_CHOICE_CONTAINS_ALL:
+ lambda val, field: set(val) == set(split_choices(field)),
+ FILTER_CHOICE_DOESNT_CONTAIN_ANY:
+ lambda val, field: not set(val) & set(split_choices(field)),
+ FILTER_CHOICE_DOESNT_CONTAIN_ALL:
+ lambda val, field: set(val) != set(split_choices(field)),
+}
+
+# Export form fields for each filter type grouping
+text_filter_field = forms.ChoiceField(label=" ", required=False,
+ choices=TEXT_FILTER_CHOICES)
+choice_filter_field = forms.ChoiceField(label=" ", required=False,
+ choices=CHOICE_FILTER_CHOICES)
+multiple_filter_field = forms.ChoiceField(label=" ", required=False,
+ choices=MULTIPLE_FILTER_CHOICES)
+date_filter_field = forms.ChoiceField(label=" ", required=False,
+ choices=DATE_FILTER_CHOICES)
+
+
+class FormForForm(forms.ModelForm):
+ field_entry_model = FieldEntry
+
+ class Meta:
+ model = FormEntry
+ exclude = ("form", "entry_time")
+
+ def __init__(self, form, context, *args, **kwargs):
+ """
+ Dynamically add each of the form fields for the given form model
+ instance and its related field model instances.
+ """
+ self.form = form
+ self.form_fields = form.fields.visible()
+ initial = kwargs.pop("initial", {})
+ # If a FormEntry instance is given to edit, stores it's field
+ # values for using as initial data.
+ field_entries = {}
+ if kwargs.get("instance"):
+ for field_entry in kwargs["instance"].fields.all():
+ field_entries[field_entry.field_id] = field_entry.value
+ super(FormForForm, self).__init__(*args, **kwargs)
+ # Create the form fields.
+ for field in self.form_fields:
+ field_key = field.slug
+ field_class = fields.CLASSES[field.field_type]
+ field_widget = fields.WIDGETS.get(field.field_type)
+ field_args = {"label": field.label, "required": field.required,
+ "help_text": field.help_text}
+ arg_names = field_class.__init__.__code__.co_varnames
+ if "max_length" in arg_names:
+ field_args["max_length"] = settings.FIELD_MAX_LENGTH
+ if "choices" in arg_names:
+ choices = list(field.get_choices())
+ if (field.field_type == fields.SELECT and
+ field.default not in [c[0] for c in choices]):
+ choices.insert(0, ("", field.placeholder_text))
+ field_args["choices"] = choices
+ if field_widget is not None:
+ field_args["widget"] = field_widget
+ #
+ # Initial value for field, in order of preference:
+ #
+ # - If a form model instance is given (eg we're editing a
+ # form response), then use the instance's value for the
+ # field.
+ # - If the developer has provided an explicit "initial"
+ # dict, use it.
+ # - The default value for the field instance as given in
+ # the admin.
+ #
+ initial_val = None
+ try:
+ initial_val = field_entries[field.id]
+ except KeyError:
+ try:
+ initial_val = initial[field_key]
+ except KeyError:
+ initial_val = Template(field.default).render(context)
+ if initial_val:
+ if field.is_a(*fields.MULTIPLE):
+ initial_val = split_choices(initial_val)
+ if field.field_type == fields.CHECKBOX:
+ initial_val = initial_val != "False"
+ self.initial[field_key] = initial_val
+ self.fields[field_key] = field_class(**field_args)
+
+ if field.field_type == fields.DOB:
+ now = datetime.now()
+ years = list(range(now.year, now.year - 120, -1))
+ self.fields[field_key].widget.years = years
+
+ # Add identifying CSS classes to the field.
+ css_class = field_class.__name__.lower()
+ if field.required:
+ css_class += " required"
+ if (settings.USE_HTML5 and
+ field.field_type != fields.CHECKBOX_MULTIPLE):
+ self.fields[field_key].widget.attrs["required"] = ""
+ self.fields[field_key].widget.attrs["class"] = css_class
+ if field.placeholder_text and not field.default:
+ text = field.placeholder_text
+ self.fields[field_key].widget.attrs["placeholder"] = text
+
+ def save(self, **kwargs):
+ """
+ Get/create a FormEntry instance and assign submitted values to
+ related FieldEntry instances for each form field.
+ """
+ entry = super(FormForForm, self).save(commit=False)
+ entry.form = self.form
+ entry.entry_time = now()
+ entry.save()
+ entry_fields = entry.fields.values_list("field_id", flat=True)
+ new_entry_fields = []
+ for field in self.form_fields:
+ field_key = field.slug
+ value = self.cleaned_data[field_key]
+ if value and self.fields[field_key].widget.needs_multipart_form:
+ value = fs.save(join("forms", str(uuid4()), value.name), value)
+ if isinstance(value, list):
+ value = ", ".join([v.strip() for v in value])
+ if field.id in entry_fields:
+ field_entry = entry.fields.get(field_id=field.id)
+ field_entry.value = value
+ field_entry.save()
+ else:
+ new = {"entry": entry, "field_id": field.id, "value": value}
+ new_entry_fields.append(self.field_entry_model(**new))
+ if new_entry_fields:
+ if django.VERSION >= (1, 4, 0):
+ self.field_entry_model.objects.bulk_create(new_entry_fields)
+ else:
+ for field_entry in new_entry_fields:
+ field_entry.save()
+ return entry
+
+ def email_to(self):
+ """
+ Return the value entered for the first field of type EmailField.
+ """
+ for field in self.form_fields:
+ if field.is_a(fields.EMAIL):
+ return self.cleaned_data[field.slug]
+ return None
+
+
+class EntriesForm(forms.Form):
+ """
+ Form with a set of fields dynamically assigned that can be used to
+ filter entries for the given ``forms.models.Form`` instance.
+ """
+
+ def __init__(self, form, request, formentry_model=FormEntry,
+ fieldentry_model=FieldEntry, *args, **kwargs):
+ """
+ Iterate through the fields of the ``forms.models.Form`` instance and
+ create the form fields required to control including the field in
+ the export (with a checkbox) or filtering the field which differs
+ across field types. User a list of checkboxes when a fixed set of
+ choices can be chosen from, a pair of date fields for date ranges,
+ and for all other types provide a textbox for text search.
+ """
+ self.form = form
+ self.request = request
+ self.formentry_model = formentry_model
+ self.fieldentry_model = fieldentry_model
+ self.form_fields = form.fields.all()
+ self.entry_time_name = str(self.formentry_model._meta.get_field(
+ "entry_time").verbose_name)
+ super(EntriesForm, self).__init__(*args, **kwargs)
+ for field in self.form_fields:
+ field_key = "field_%s" % field.id
+ # Checkbox for including in export.
+ self.fields["%s_export" % field_key] = forms.BooleanField(
+ label=field.label, initial=True, required=False)
+ if field.is_a(*fields.CHOICES):
+ # A fixed set of choices to filter by.
+ if field.is_a(fields.CHECKBOX):
+ choices = ((True, _("Checked")), (False, _("Not checked")))
+ else:
+ choices = field.get_choices()
+ contains_field = forms.MultipleChoiceField(label=" ",
+ choices=choices, widget=forms.CheckboxSelectMultiple(),
+ required=False)
+ self.fields["%s_filter" % field_key] = choice_filter_field
+ self.fields["%s_contains" % field_key] = contains_field
+ elif field.is_a(*fields.MULTIPLE):
+ # A fixed set of choices to filter by, with multiple
+ # possible values in the entry field.
+ contains_field = forms.MultipleChoiceField(label=" ",
+ choices=field.get_choices(),
+ widget=forms.CheckboxSelectMultiple(),
+ required=False)
+ self.fields["%s_filter" % field_key] = multiple_filter_field
+ self.fields["%s_contains" % field_key] = contains_field
+ elif field.is_a(*fields.DATES):
+ # A date range to filter by.
+ self.fields["%s_filter" % field_key] = date_filter_field
+ self.fields["%s_from" % field_key] = forms.DateField(
+ label=" ", widget=SelectDateWidget(), required=False)
+ self.fields["%s_to" % field_key] = forms.DateField(
+ label=_("and"), widget=SelectDateWidget(), required=False)
+ else:
+ # Text box for search term to filter by.
+ contains_field = forms.CharField(label=" ", required=False)
+ self.fields["%s_filter" % field_key] = text_filter_field
+ self.fields["%s_contains" % field_key] = contains_field
+ # Add ``FormEntry.entry_time`` as a field.
+ field_key = "field_0"
+ label = self.formentry_model._meta.get_field("entry_time").verbose_name
+ self.fields["%s_export" % field_key] = forms.BooleanField(
+ initial=True, label=label, required=False)
+ self.fields["%s_filter" % field_key] = date_filter_field
+ self.fields["%s_from" % field_key] = forms.DateField(
+ label=" ", widget=SelectDateWidget(), required=False)
+ self.fields["%s_to" % field_key] = forms.DateField(
+ label=_("and"), widget=SelectDateWidget(), required=False)
+
+ def __iter__(self):
+ """
+ Yield pairs of include checkbox / filters for each field.
+ """
+ for field_id in [f.id for f in self.form_fields] + [0]:
+ prefix = "field_%s_" % field_id
+ fields = [f for f in super(EntriesForm, self).__iter__()
+ if f.name.startswith(prefix)]
+ yield fields[0], fields[1], fields[2:]
+
+ def posted_data(self, field):
+ """
+ Wrapper for self.cleaned_data that returns True on
+ field_id_export fields when the form hasn't been posted to,
+ to facilitate show/export URLs that export all entries without
+ a form submission.
+ """
+ try:
+ return self.cleaned_data[field]
+ except (AttributeError, KeyError):
+ return field.endswith("_export")
+
+ def columns(self):
+ """
+ Returns the list of selected column names.
+ """
+ fields = [f.label for f in self.form_fields
+ if self.posted_data("field_%s_export" % f.id)]
+ if self.posted_data("field_0_export"):
+ fields.append(self.entry_time_name)
+ return fields
+
+ def rows(self, csv=False):
+ """
+ Returns each row based on the selected criteria.
+ """
+
+ # Store the index of each field against its ID for building each
+ # entry row with columns in the correct order. Also store the IDs of
+ # fields with a type of FileField or Date-like for special handling of
+ # their values.
+ field_indexes = {}
+ file_field_ids = []
+ date_field_ids = []
+ for field in self.form_fields:
+ if self.posted_data("field_%s_export" % field.id):
+ field_indexes[field.id] = len(field_indexes)
+ if field.is_a(fields.FILE):
+ file_field_ids.append(field.id)
+ elif field.is_a(*fields.DATES):
+ date_field_ids.append(field.id)
+ num_columns = len(field_indexes)
+ include_entry_time = self.posted_data("field_0_export")
+ if include_entry_time:
+ num_columns += 1
+
+ # Get the field entries for the given form and filter by entry_time
+ # if specified.
+ model = self.fieldentry_model
+ field_entries = model.objects.filter(entry__form=self.form
+ ).order_by("-entry__id").select_related("entry")
+ if self.posted_data("field_0_filter") == FILTER_CHOICE_BETWEEN:
+ time_from = self.posted_data("field_0_from")
+ time_to = self.posted_data("field_0_to")
+ if time_from and time_to:
+ field_entries = field_entries.filter(
+ entry__entry_time__range=(time_from, time_to))
+
+ # Loop through each field value ordered by entry, building up each
+ # entry as a row. Use the ``valid_row`` flag for marking a row as
+ # invalid if it fails one of the filtering criteria specified.
+ current_entry = None
+ current_row = None
+ valid_row = True
+ for field_entry in field_entries:
+ if field_entry.entry_id != current_entry:
+ # New entry, write out the current row and start a new one.
+ if valid_row and current_row is not None:
+ if not csv:
+ current_row.insert(0, current_entry)
+ yield current_row
+ current_entry = field_entry.entry_id
+ current_row = [""] * num_columns
+ valid_row = True
+ if include_entry_time:
+ current_row[-1] = field_entry.entry.entry_time
+ field_value = field_entry.value or ""
+ # Check for filter.
+ field_id = field_entry.field_id
+ filter_type = self.posted_data("field_%s_filter" % field_id)
+ filter_args = None
+ if filter_type:
+ if filter_type == FILTER_CHOICE_BETWEEN:
+ f, t = "field_%s_from" % field_id, "field_%s_to" % field_id
+ filter_args = [self.posted_data(f), self.posted_data(t)]
+ else:
+ field_name = "field_%s_contains" % field_id
+ filter_args = self.posted_data(field_name)
+ if filter_args:
+ filter_args = [filter_args]
+ if filter_args:
+ # Convert dates before checking filter.
+ if field_id in date_field_ids:
+ try:
+ y, m, d = field_value.split(" ")[0].split("-")
+ except ValueError:
+ filter_args.append(field_value)
+ else:
+ dte = date(int(y), int(m), int(d))
+ filter_args.append(dte)
+ else:
+ filter_args.append(field_value)
+ filter_func = FILTER_FUNCS[filter_type]
+ if not filter_func(*filter_args):
+ valid_row = False
+ # Create download URL for file fields.
+ if field_entry.value and field_id in file_field_ids:
+ url = reverse("admin:form_file", args=(field_entry.id,))
+ field_value = self.request.build_absolute_uri(url)
+ if not csv:
+ parts = (field_value, split(field_entry.value)[1])
+ field_value = mark_safe("<a href=\"%s\">%s</a>" % parts)
+ # Only use values for fields that were selected.
+ try:
+ current_row[field_indexes[field_id]] = field_value
+ except KeyError:
+ pass
+ # Output the final row.
+ if valid_row and current_row is not None:
+ if not csv:
+ current_row.insert(0, current_entry)
+ yield current_row
--- /dev/null
+"Project-Id-Version: django-forms-builder\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-09-04 15:42+0200\n"
+"PO-Revision-Date: Wed Sep 04 2013 16:25:17 GMT+0200\n"
+"Last-Translator: Sven Rojek <sven@rojek.de>\n"
+"Language-Team: Sven Rojek <sven@rojek.de>\n"
+"Language: German\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "Email"
+msgstr "E-Mail"
+
+#, fuzzy
+msgid "Slug"
+msgstr "Kurzform"
+
+msgid "Sites"
+msgstr "Seiten"
+
+msgid "1 entry deleted"
+msgid_plural "%(count)s entries deleted"
+msgstr[0] "1 Eintrag gelöscht"
+msgstr[1] "%(count)s Einträge gelöscht"
+
+msgid "View Entries"
+msgstr "Einträge anzeigen"
+
+msgid "Single line text"
+msgstr "Einzeiliges Eingabefeld"
+
+msgid "Multi line text"
+msgstr "Mehrzeiliger Eingabebereich"
+
+msgid "Number"
+msgstr "Nummer"
+
+msgid "URL"
+msgstr "URL"
+
+msgid "Check box"
+msgstr "Checkbox"
+
+msgid "Check boxes"
+msgstr "Checkboxen"
+
+msgid "Drop down"
+msgstr "Dropdown"
+
+msgid "Multi select"
+msgstr "Auswahllisten mit Mehrfachauswahl"
+
+msgid "Radio buttons"
+msgstr "Radio-Buttons"
+
+msgid "File upload"
+msgstr "Datei-Upload"
+
+msgid "Date"
+msgstr "Datum"
+
+msgid "Date/time"
+msgstr "Datum/Uhrzeit"
+
+msgid "Date of birth"
+msgstr "Geburtsdatum"
+
+msgid "Hidden"
+msgstr "Versteckt"
+
+msgid "Nothing"
+msgstr "Nichts"
+
+msgid "Contains"
+msgstr "Enthält"
+
+msgid "Doesn't contain"
+msgstr "Enthält nicht"
+
+msgid "Equals"
+msgstr "Gleich"
+
+msgid "Doesn't equal"
+msgstr "Ungleich"
+
+msgid "Equals any"
+msgstr "Gleich"
+
+msgid "Doesn't equal any"
+msgstr "Ungleich"
+
+msgid "Contains any"
+msgstr "Enthält"
+
+msgid "Contains all"
+msgstr "Enthält alle"
+
+msgid "Doesn't contain any"
+msgstr "Enthält nicht"
+
+msgid "Doesn't contain all"
+msgstr "Enthält keines"
+
+msgid "Is between"
+msgstr "Zwischen"
+
+msgid "Checked"
+msgstr "Ausgewählt"
+
+msgid "Not checked"
+msgstr "Nicht ausgewählt"
+
+msgid "and"
+msgstr "und"
+
+msgid "Draft"
+msgstr "Entwurf"
+
+msgid "Published"
+msgstr "Veröffentlicht"
+
+msgid "Title"
+msgstr "Titel"
+
+msgid "Intro"
+msgstr "Einführung"
+
+msgid "Button text"
+msgstr "Button-Text"
+
+msgid "Submit"
+msgstr "Senden"
+
+msgid "Response"
+msgstr "Antwort"
+
+msgid "Status"
+msgstr "Status"
+
+msgid "Published from"
+msgstr "Veröffentlicht von"
+
+msgid "With published selected, won't be shown until this time"
+msgstr "Mit Veröffentlicht ausgewählt, wird bis zu diesem Zeitpunkt nicht angezeigt"
+
+msgid "Expires on"
+msgstr "Gültig bis"
+
+msgid "With published selected, won't be shown after this time"
+msgstr "Mit Veröffentlicht ausgewählt, wird nach diesem Zeitpunkt nicht mehr angezeigt"
+
+msgid "Login required"
+msgstr "Anmeldung erforderlich"
+
+msgid "If checked, only logged in users can view the form"
+msgstr "Wenn ausgewählt, können nur eingeloggte Nutzer das Formular sehen."
+
+msgid "Send email"
+msgstr "E-Mail versenden"
+
+msgid "If checked, the person entering the form will be sent an email"
+msgstr "Wenn ausgewählt, wird an die ausfüllende Person eine E-Mail gesendet"
+
+msgid "From address"
+msgstr "Absenderadresse"
+
+msgid "The address the email will be sent from"
+msgstr "Die E-Mail-Adresse von welcher versendet wird"
+
+msgid "Send copies to"
+msgstr "Sende Kopien an"
+
+msgid "One or more email addresses, separated by commas"
+msgstr "Eine oder mehrere E-Mail Adressen, getrennt durch Kommata"
+
+msgid "Subject"
+msgstr "Betreff"
+
+msgid "Message"
+msgstr "Nachricht"
+
+msgid "Form"
+msgstr "Formular"
+
+msgid "Forms"
+msgstr "Formulare"
+
+msgid "View form on site"
+msgstr "Formular auf der Seite anzeigen"
+
+msgid "Filter entries"
+msgstr "Filter Einträge"
+
+msgid "View all entries"
+msgstr "Zeige alle Einträge"
+
+msgid "Export all entries"
+msgstr "Alle Einträge exportieren"
+
+msgid "Placeholder Text"
+msgstr "Platzhaltertext"
+
+msgid "Label"
+msgstr "Beschriftung"
+
+msgid "Type"
+msgstr "Typ"
+
+msgid "Required"
+msgstr "Benötigt"
+
+msgid "Visible"
+msgstr "Sichtbar"
+
+msgid "Choices"
+msgstr "Auswahl"
+
+msgid "Default value"
+msgstr "Standardwert"
+
+msgid "Help text"
+msgstr "Hilfetext"
+
+msgid "Field"
+msgstr "Feld"
+
+msgid "Fields"
+msgstr "Felder"
+
+msgid "Form entry"
+msgstr "Formular Eintrag"
+
+msgid "Form entries"
+msgstr "Formular Einträge"
+
+msgid "Form field entry"
+msgstr "Formularfeld Eintrag"
+
+msgid "Form field entries"
+msgstr "Formularfeld Einträge"
+
+msgid "Order"
+msgstr "Reihenfolge"
+
+msgid "View entries"
+msgstr "Zeige Einträge"
+
+msgid "History"
+msgstr "Geschichte"
+
+msgid "View on site"
+msgstr "Auf Seite anzeigen"
+
+msgid "No entries selected"
+msgstr "Keine Einträge ausgewählt"
+
+msgid "Delete selected entries?"
+msgstr "Ausgewählte Einträge löschen?"
+
+msgid "Home"
+msgstr "Home"
+
+msgid "Include"
+msgstr "Enthalten"
+
+msgid "Filter by"
+msgstr "Filter nach"
+
+msgid "All"
+msgstr "Alle"
+
+msgid "Back to form"
+msgstr "Zurück zum Formular"
+
+msgid "Export CSV"
+msgstr "Als CSV exportieren"
+
+msgid "Export XLS"
+msgstr "Als XLS exportieren"
+
+msgid "Entries"
+msgstr "Einträge"
+
+msgid "Delete selected"
+msgstr "Lösche ausgewählte"
+
+msgid "No entries to display"
+msgstr "Keine Einträge zum Anzeigen"
--- /dev/null
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# , 2011.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-11-27 20:19-0500\n"
+"PO-Revision-Date: 2011-11-28 19:07-0500\n"
+"Last-Translator: \n"
+"Language-Team: diegeus9 <diego@diegue.us>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"X-Generator: Lokalize 1.2\n"
+
+#: admin.py:29 fields.py:29
+msgid "Email"
+msgstr "Email"
+
+#: admin.py:33
+msgid "Sites"
+msgstr "Sitios"
+
+#: admin.py:117
+#, python-format
+msgid "1 entry deleted"
+msgid_plural "%(count)s entries deleted"
+msgstr[0] "%(count)s entries deleted"
+
+#: admin.py:121
+msgid "View Entries"
+msgstr "Ver entradas"
+
+#: fields.py:27
+msgid "Single line text"
+msgstr "Texto de una sola linea"
+
+#: fields.py:28
+msgid "Multi line text"
+msgstr "Texto de varias lineas"
+
+#: fields.py:30
+msgid "Number"
+msgstr "Número"
+
+#: fields.py:31
+msgid "URL"
+msgstr "URL"
+
+#: fields.py:32
+msgid "Check box"
+msgstr "Casilla de verificación"
+
+#: fields.py:33
+msgid "Check boxes"
+msgstr "Casillas de verificación"
+
+#: fields.py:34
+msgid "Drop down"
+msgstr "Desplegable"
+
+#: fields.py:35
+msgid "Multi select"
+msgstr "Selección múltiple"
+
+#: fields.py:36
+msgid "Radio buttons"
+msgstr "Botón de selección única"
+
+#: fields.py:37
+msgid "File upload"
+msgstr "Carga de archivo"
+
+#: fields.py:38
+msgid "Date"
+msgstr "Fecha"
+
+#: fields.py:39 models.py:210
+msgid "Date/time"
+msgstr "Fecha/Hora"
+
+#: fields.py:40
+msgid "Hidden"
+msgstr "Oculto"
+
+#: forms.py:27 forms.py:35 forms.py:41
+msgid "Nothing"
+msgstr "Nada"
+
+#: forms.py:28
+msgid "Contains"
+msgstr "Contiene"
+
+#: forms.py:29
+msgid "Doesn't contain"
+msgstr "No contiene"
+
+#: forms.py:30 forms.py:36
+msgid "Equals"
+msgstr "Igual"
+
+#: forms.py:31 forms.py:37
+msgid "Doesn't equal"
+msgstr "No es igual"
+
+#: forms.py:42
+msgid "Is between"
+msgstr "Está entre"
+
+#: forms.py:165
+msgid "Checked"
+msgstr "Comprobado"
+
+#: forms.py:165
+msgid "Not checked"
+msgstr "No comprobado"
+
+#: forms.py:179 forms.py:194
+msgid "and"
+msgstr "y"
+
+#: models.py:54
+msgid "Title"
+msgstr "Título"
+
+#: models.py:56
+msgid "Intro"
+msgstr "Introducción"
+
+#: models.py:57
+msgid "Button text"
+msgstr "Botón de texto"
+
+#: models.py:58
+msgid "Submit"
+msgstr "Enviar"
+
+#: models.py:59
+msgid "Response"
+msgstr "Respuesta"
+
+#: models.py:60
+msgid "Status"
+msgstr "Estado"
+
+#: models.py:62
+msgid "Published from"
+msgstr "Pulbicado a partir de"
+
+#: models.py:63
+msgid "With published selected, won't be shown until this time"
+msgstr ""
+"Con la opción \"published\" (publicado) seleccionado, no será mostrado hasta "
+"esta fecha"
+
+#: models.py:65
+msgid "Expires on"
+msgstr "Expira el"
+
+#: models.py:66
+msgid "With published selected, won't be shown after this time"
+msgstr ""
+"Con la opción \"published\" (publicado) seleccionado, no será mostrado "
+"después de esta fecha"
+
+#: models.py:68
+msgid "Login required"
+msgstr "Ingreso requerido"
+
+#: models.py:69
+msgid "If checked, only logged in users can view the form"
+msgstr ""
+"Si es seleccionado, solo usuarios registrados pueden ver este formulario"
+
+#: models.py:70
+msgid "Send email"
+msgstr "Enviar correo electrónico"
+
+#: models.py:71
+msgid "If checked, the person entering the form will be sent an email"
+msgstr "Si es selecconado, la persona llenando el formulario enviará un correo"
+
+#: models.py:72
+msgid "From address"
+msgstr "Dirección de remitente"
+
+#: models.py:73
+msgid "The address the email will be sent from"
+msgstr "La dirección del remitente que será enviada"
+
+#: models.py:74
+msgid "Send copies to"
+msgstr "Enviar copias a"
+
+#: models.py:75
+msgid "One or more email addresses, separated by commas"
+msgstr "Una o más direcciones de correo electrónico, separadas por coma"
+
+#: models.py:77
+msgid "Subject"
+msgstr "Asunto"
+
+#: models.py:78
+msgid "Message"
+msgstr "Mensaje"
+
+#: models.py:83
+msgid "Form"
+msgstr "Formulario"
+
+#: models.py:84
+msgid "Forms"
+msgstr "Formularios"
+
+#: models.py:123
+msgid "View form on site"
+msgstr "Ver formulario en el sitio"
+
+#: models.py:124 templates/admin/forms/change_form.html:8
+#: templates/admin/forms/entries.html:119
+msgid "View entries"
+msgstr "Ver entradas"
+
+#: models.py:144
+msgid "Placeholder Text"
+msgstr "Texto inicial"
+
+#: models.py:151
+msgid "Label"
+msgstr "Etiqueta"
+
+#: models.py:152
+msgid "Type"
+msgstr "Tipo"
+
+#: models.py:153
+msgid "Required"
+msgstr "Requerido"
+
+#: models.py:154
+msgid "Visible"
+msgstr "Visible"
+
+#: models.py:155
+msgid "Choices"
+msgstr "Opciones"
+
+#: models.py:160
+msgid "Default value"
+msgstr "Valor por omisión"
+
+#: models.py:163
+msgid "Help text"
+msgstr "Texto de ayuda"
+
+#: models.py:168 templates/admin/forms/entries.html:90
+msgid "Field"
+msgstr "Campo"
+
+#: models.py:169
+msgid "Fields"
+msgstr "Campos"
+
+#: models.py:213
+msgid "Form entry"
+msgstr "Entrada de Formulario"
+
+#: models.py:214
+msgid "Form entries"
+msgstr "Entradas de Formulario"
+
+#: models.py:226
+msgid "Form field entry"
+msgstr "Entrada de campo de formulario"
+
+#: models.py:227
+msgid "Form field entries"
+msgstr "Entradas de campo de formulario"
+
+#: templates/admin/forms/change_form.html:9
+msgid "History"
+msgstr "Historia"
+
+#: templates/admin/forms/change_form.html:10
+msgid "View on site"
+msgstr "Ver en el sitio"
+
+#: templates/admin/forms/entries.html:63
+msgid "No entries selected"
+msgstr "Ninguna entrada seleccionada"
+
+#: templates/admin/forms/entries.html:66
+msgid "Delete selected entries?"
+msgstr "Eliminar las entradas seleccionadas?"
+
+#: templates/admin/forms/entries.html:76
+msgid "Home"
+msgstr "Inicio"
+
+#: templates/admin/forms/entries.html:91
+msgid "Include"
+msgstr "Incluir"
+
+#: templates/admin/forms/entries.html:92
+msgid "Filter by"
+msgstr "Filtrar"
+
+#: templates/admin/forms/entries.html:110
+msgid "All"
+msgstr "Todos"
+
+#: templates/admin/forms/entries.html:118
+#: templates/admin/forms/entries.html:156
+msgid "Back to form"
+msgstr "Volver al formulario"
+
+#: templates/admin/forms/entries.html:120
+msgid "Export CSV"
+msgstr "Exportar como CSV"
+
+#: templates/admin/forms/entries.html:123
+msgid "Entries"
+msgstr "Entradas"
+
+#: templates/admin/forms/entries.html:157
+msgid "Delete selected"
+msgstr "Eliminar seleccionados"
+
+#: templates/admin/forms/entries.html:161
+msgid "No entries to display"
+msgstr "Ninguna entrada que mostrar"
+
+
--- /dev/null
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: django-forms-builder\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-06-27 12:44+0200\n"
+"PO-Revision-Date: 2012-06-27 12:45+0100\n"
+"Last-Translator: Dominique Guardiola <dguardiola@quinode.fr>\n"
+"Language-Team: Quindoe <contact@quinode.fr>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"X-Poedit-Language: French\n"
+"X-Poedit-Country: FRANCE\n"
+
+#: admin.py:29
+#: fields.py:29
+msgid "Email"
+msgstr "Courriel"
+
+#: admin.py:33
+msgid "Sites"
+msgstr "Sites"
+
+#: admin.py:125
+#, python-format
+msgid "1 entry deleted"
+msgid_plural "%(count)s entries deleted"
+msgstr[0] "Une réponse effacée"
+msgstr[1] "%(count)s réponses effacées"
+
+#: admin.py:129
+msgid "View Entries"
+msgstr "Voir les réponses"
+
+#: fields.py:27
+msgid "Single line text"
+msgstr "Texte sur une ligne"
+
+#: fields.py:28
+msgid "Multi line text"
+msgstr "Texte sur plusieurs lignes"
+
+#: fields.py:30
+msgid "Number"
+msgstr "Numéro"
+
+#: fields.py:31
+msgid "URL"
+msgstr "URL"
+
+#: fields.py:32
+msgid "Check box"
+msgstr "Case à cocher"
+
+#: fields.py:33
+msgid "Check boxes"
+msgstr "Cases à cocher"
+
+#: fields.py:34
+msgid "Drop down"
+msgstr "Menu déroulant"
+
+#: fields.py:35
+msgid "Multi select"
+msgstr "Sélection multiple"
+
+#: fields.py:36
+msgid "Radio buttons"
+msgstr "Choix exclusifs"
+
+#: fields.py:37
+msgid "File upload"
+msgstr "Envoi de fichier"
+
+#: fields.py:38
+msgid "Date"
+msgstr "Date"
+
+#: fields.py:39
+#: models.py:218
+msgid "Date/time"
+msgstr "Date et heure"
+
+#: fields.py:40
+msgid "Hidden"
+msgstr "Caché"
+
+#: forms.py:30
+#: forms.py:38
+#: forms.py:44
+msgid "Nothing"
+msgstr "Rien"
+
+#: forms.py:31
+msgid "Contains"
+msgstr "Contient"
+
+#: forms.py:32
+msgid "Doesn't contain"
+msgstr "Ne contient pas"
+
+#: forms.py:33
+#: forms.py:39
+msgid "Equals"
+msgstr "Est égal à"
+
+#: forms.py:34
+#: forms.py:40
+msgid "Doesn't equal"
+msgstr "N'est pas égal à"
+
+#: forms.py:45
+msgid "Is between"
+msgstr "se situe entre"
+
+#: forms.py:212
+msgid "Checked"
+msgstr "coché"
+
+#: forms.py:212
+msgid "Not checked"
+msgstr "non coché"
+
+#: forms.py:226
+#: forms.py:241
+msgid "and"
+msgstr "et"
+
+#: models.py:18
+msgid "Draft"
+msgstr "Brouillon"
+
+#: models.py:19
+msgid "Published"
+msgstr "Publié"
+
+#: models.py:55
+msgid "Title"
+msgstr "Titre"
+
+#: models.py:57
+msgid "Intro"
+msgstr "Intro"
+
+#: models.py:58
+msgid "Button text"
+msgstr "Texte du bouton"
+
+#: models.py:59
+msgid "Submit"
+msgstr "Envoyer"
+
+#: models.py:60
+#, fuzzy
+msgid "Response"
+msgstr "Message à la réception"
+
+#: models.py:61
+msgid "Status"
+msgstr "Statut"
+
+#: models.py:63
+msgid "Published from"
+msgstr "Publié depuis"
+
+#: models.py:64
+msgid "With published selected, won't be shown until this time"
+msgstr "Avec \"publié\" sélectionné, ne sera pas publié avant cette date"
+
+#: models.py:66
+msgid "Expires on"
+msgstr "Expire le"
+
+#: models.py:67
+msgid "With published selected, won't be shown after this time"
+msgstr "Avec \"publié\" sélectionné, ne sera pas publié avant cette date"
+
+#: models.py:69
+msgid "Login required"
+msgstr "Identification requise"
+
+#: models.py:70
+msgid "If checked, only logged in users can view the form"
+msgstr "Si coché, seules les visiteurs identifiés pourront voir le formulaire"
+
+#: models.py:71
+msgid "Send email"
+msgstr "Envoyer un courriel"
+
+#: models.py:72
+msgid "If checked, the person entering the form will be sent an email"
+msgstr "Si coché, les personnes répondant au formulaire recevront un courriel"
+
+#: models.py:73
+msgid "From address"
+msgstr "Adresse d'expéditeur"
+
+#: models.py:74
+msgid "The address the email will be sent from"
+msgstr "L'adresse à partir de laquelle sera envoyé le courriel"
+
+#: models.py:75
+msgid "Send copies to"
+msgstr "Envoyer des copies à"
+
+#: models.py:76
+msgid "One or more email addresses, separated by commas"
+msgstr "Une ou plusieurs adresses email séparées par des virgules"
+
+#: models.py:78
+msgid "Subject"
+msgstr "Sujet"
+
+#: models.py:79
+msgid "Message"
+msgstr "Message"
+
+#: models.py:84
+msgid "Form"
+msgstr "Formulaire"
+
+#: models.py:85
+msgid "Forms"
+msgstr "Formulaires"
+
+#: models.py:124
+msgid "View form on site"
+msgstr "Voir sur le site"
+
+#: models.py:125
+msgid "Filter entries"
+msgstr "Filtrer les réponses"
+
+#: models.py:126
+msgid "View all entries"
+msgstr "Voir toutes les réponses"
+
+#: models.py:127
+msgid "Export all entries"
+msgstr "Exporter toutes les réponses"
+
+#: models.py:149
+msgid "Label"
+msgstr "Libellé"
+
+#: models.py:150
+msgid "Slug"
+msgstr "Slug"
+
+#: models.py:152
+msgid "Type"
+msgstr "Type"
+
+#: models.py:153
+msgid "Required"
+msgstr "Obligatoire"
+
+#: models.py:154
+msgid "Visible"
+msgstr "Visible"
+
+#: models.py:155
+msgid "Choices"
+msgstr "Choix"
+
+#: models.py:160
+msgid "Default value"
+msgstr "Valeur par défaut"
+
+#: models.py:162
+msgid "Placeholder Text"
+msgstr "Texte par défaut"
+
+#: models.py:164
+msgid "Help text"
+msgstr "Texte d'aide"
+
+#: models.py:169
+#: templates/admin/forms/entries.html:87
+msgid "Field"
+msgstr "Champ"
+
+#: models.py:170
+msgid "Fields"
+msgstr "Champs"
+
+#: models.py:221
+msgid "Form entry"
+msgstr "Réponse au formulaire"
+
+#: models.py:222
+msgid "Form entries"
+msgstr "Réponses au formulaire"
+
+#: models.py:236
+msgid "Form field entry"
+msgstr "Réponse à un champ de formulaire"
+
+#: models.py:237
+msgid "Form field entries"
+msgstr "Réponses à un champ de formulaire"
+
+#: models.py:265
+msgid "Order"
+msgstr "Ordre"
+
+#: templates/admin/forms/change_form.html:10
+#: templates/admin/forms/entries.html:116
+msgid "View entries"
+msgstr "Voir les réponses"
+
+#: templates/admin/forms/change_form.html:13
+msgid "History"
+msgstr "Historique"
+
+#: templates/admin/forms/change_form.html:17
+msgid "View on site"
+msgstr "Voir sur le site"
+
+#: templates/admin/forms/entries.html:61
+msgid "No entries selected"
+msgstr "Pas de réponses sélectionnées"
+
+#: templates/admin/forms/entries.html:64
+msgid "Delete selected entries?"
+msgstr "Effacer les réponses sélectionnées?"
+
+#: templates/admin/forms/entries.html:73
+msgid "Home"
+msgstr "Accueil"
+
+#: templates/admin/forms/entries.html:88
+msgid "Include"
+msgstr "Inclure"
+
+#: templates/admin/forms/entries.html:89
+msgid "Filter by"
+msgstr "Filtrer par"
+
+#: templates/admin/forms/entries.html:107
+msgid "All"
+msgstr "Tous"
+
+#: templates/admin/forms/entries.html:115
+#: templates/admin/forms/entries.html:155
+msgid "Back to form"
+msgstr "Retour au formulaire"
+
+#: templates/admin/forms/entries.html:117
+msgid "Export CSV"
+msgstr "Exporter en tableur CSV"
+
+#: templates/admin/forms/entries.html:120
+msgid "Entries"
+msgstr "Réponses"
+
+#: templates/admin/forms/entries.html:156
+msgid "Delete selected"
+msgstr "Effacer la sélection"
+
+#: templates/admin/forms/entries.html:160
+msgid "No entries to display"
+msgstr "Pas de réponses à afficher"
+
--- /dev/null
+# Norwegian translation for stephenmcd's django-forms-builder
+# This file is distributed under the same license as the PACKAGE package.
+# Sindre Sorhus <sindresorhus@gmail.com>, 2011.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: django-forms-builder\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-10-12 14:14+0200\n"
+"PO-Revision-Date: 2011-10-12 14:38+0100\n"
+"Last-Translator: Sindre Sorhus <sindresorhus@gmail.com>\n"
+"Language-Team: <sindresorhus@gmail.com>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"X-Poedit-Language: Norwegian Bokmal\n"
+"X-Poedit-Country: NORWAY\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: admin.py:28
+#: fields.py:27
+msgid "Email"
+msgstr "E-post"
+
+#: admin.py:32
+msgid "Sites"
+msgstr "Sider"
+
+#: admin.py:114
+#, python-format
+msgid "1 entry deleted"
+msgid_plural "%(count)s entries deleted"
+msgstr[0] "Slettet ett innlegg"
+msgstr[1] "Slettet %(count)s innlegg"
+
+#: admin.py:119
+msgid "View Entries"
+msgstr "Vis innlegg"
+
+#: fields.py:25
+msgid "Single line text"
+msgstr "Tekstfelt"
+
+#: fields.py:26
+msgid "Multi line text"
+msgstr "Tekstområde"
+
+#: fields.py:28
+msgid "Check box"
+msgstr "Avmerkingsboks"
+
+#: fields.py:29
+msgid "Check boxes"
+msgstr "Avmerkingsbokser"
+
+#: fields.py:30
+msgid "Drop down"
+msgstr "Rullegardinmeny"
+
+#: fields.py:31
+msgid "Multi select"
+msgstr "Flervalgmeny"
+
+#: fields.py:32
+msgid "Radio buttons"
+msgstr "Radioknapp"
+
+#: fields.py:33
+msgid "File upload"
+msgstr "Filopplasting"
+
+#: fields.py:34
+msgid "Date"
+msgstr "Dato"
+
+#: fields.py:35
+#: models.py:210
+msgid "Date/time"
+msgstr "Dato/tid"
+
+#: fields.py:36
+msgid "Hidden"
+msgstr "Skjult"
+
+#: forms.py:27
+#: forms.py:35
+#: forms.py:41
+msgid "Nothing"
+msgstr "Ingenting"
+
+#: forms.py:28
+msgid "Contains"
+msgstr "Inneholder"
+
+#: forms.py:29
+msgid "Doesn't contain"
+msgstr "Inneholder ikke"
+
+#: forms.py:30
+#: forms.py:36
+msgid "Equals"
+msgstr "Er lik"
+
+#: forms.py:31
+#: forms.py:37
+msgid "Doesn't equal"
+msgstr "Er ikke lik"
+
+#: forms.py:42
+msgid "Is between"
+msgstr "Er mellom"
+
+#: forms.py:165
+msgid "Checked"
+msgstr "Valgt"
+
+#: forms.py:165
+msgid "Not checked"
+msgstr "Ikke valgt"
+
+#: forms.py:179
+#: forms.py:194
+msgid "and"
+msgstr "og"
+
+#: models.py:54
+msgid "Title"
+msgstr "Tittel"
+
+#: models.py:56
+msgid "Intro"
+msgstr "Intro"
+
+#: models.py:57
+msgid "Button text"
+msgstr "Knapp tekst"
+
+#: models.py:58
+msgid "Submit"
+msgstr "Send"
+
+#: models.py:59
+msgid "Response"
+msgstr "Respons"
+
+#: models.py:60
+msgid "Status"
+msgstr "Status"
+
+#: models.py:62
+msgid "Published from"
+msgstr "Publisert fra"
+
+#: models.py:63
+msgid "With published selected, won't be shown until this time"
+msgstr "Vil ikke bli vist før denne tiden hvis publisert er valgt"
+
+#: models.py:65
+msgid "Expires on"
+msgstr "Utløper"
+
+#: models.py:66
+msgid "With published selected, won't be shown after this time"
+msgstr "Vil ikke bli vist etter denne tiden hvis publisert er valgt"
+
+#: models.py:68
+msgid "Login required"
+msgstr "Krever innlogging"
+
+#: models.py:69
+msgid "If checked, only logged in users can view the form"
+msgstr "Bare innloggede brukere se skjemaet hvis valgt"
+
+#: models.py:70
+msgid "Send email"
+msgstr "Send e-post"
+
+#: models.py:71
+msgid "If checked, the person entering the form will be sent an email"
+msgstr "Innsenderen vil bli sendt en e-post hvis valgt"
+
+#: models.py:72
+msgid "From address"
+msgstr "Fra adresse"
+
+#: models.py:73
+msgid "The address the email will be sent from"
+msgstr "Adressen e-posten vil bli sendt fra"
+
+#: models.py:74
+msgid "Send copies to"
+msgstr "Send kopi til"
+
+#: models.py:75
+msgid "One or more email addresses, separated by commas"
+msgstr "En eller flere e-postadresser, adskilt med komma"
+
+#: models.py:77
+msgid "Subject"
+msgstr "Emne"
+
+#: models.py:78
+msgid "Message"
+msgstr "Melding"
+
+#: models.py:83
+msgid "Form"
+msgstr "Skjema"
+
+#: models.py:84
+msgid "Forms"
+msgstr "Skjemaer"
+
+#: models.py:123
+msgid "View form on site"
+msgstr "Vis skjema på siden"
+
+#: models.py:124
+#: templates/admin/forms/change_form.html:8
+#: templates/admin/forms/entries.html:119
+msgid "View entries"
+msgstr "Vis innlegg"
+
+#: models.py:144
+msgid "Placeholder Text"
+msgstr "Plassholdertekst"
+
+#: models.py:151
+msgid "Label"
+msgstr "Etikett"
+
+#: models.py:152
+msgid "Type"
+msgstr "Type"
+
+#: models.py:153
+msgid "Required"
+msgstr "Påkrevd"
+
+#: models.py:154
+msgid "Visible"
+msgstr "Synlig"
+
+#: models.py:155
+msgid "Choices"
+msgstr "Valg"
+
+#: models.py:160
+msgid "Default value"
+msgstr "Standard verdi"
+
+#: models.py:163
+msgid "Help text"
+msgstr "Hjelpetekst"
+
+#: models.py:168
+#: templates/admin/forms/entries.html:90
+msgid "Field"
+msgstr "Felt"
+
+#: models.py:169
+msgid "Fields"
+msgstr "Felter"
+
+#: models.py:213
+msgid "Form entry"
+msgstr "Skjemautfylling"
+
+#: models.py:214
+msgid "Form entries"
+msgstr "Skjema innlegg"
+
+#: models.py:226
+msgid "Form field entry"
+msgstr "Skjemafelt oppføring"
+
+#: models.py:227
+msgid "Form field entries"
+msgstr "Skjemafeltoppføringer"
+
+#: templates/admin/forms/change_form.html:9
+msgid "History"
+msgstr "Historie"
+
+#: templates/admin/forms/change_form.html:10
+msgid "View on site"
+msgstr "Vis på siden"
+
+#: templates/admin/forms/entries.html:63
+msgid "No entries selected"
+msgstr "Ingen innlegg er valgt"
+
+#: templates/admin/forms/entries.html:66
+msgid "Delete selected entries?"
+msgstr "Slett valgte innlegg?"
+
+#: templates/admin/forms/entries.html:76
+msgid "Home"
+msgstr "Hjem"
+
+#: templates/admin/forms/entries.html:91
+msgid "Include"
+msgstr "Inkluder"
+
+#: templates/admin/forms/entries.html:92
+msgid "Filter by"
+msgstr "Filtrer etter"
+
+#: templates/admin/forms/entries.html:110
+msgid "All"
+msgstr "Alle"
+
+#: templates/admin/forms/entries.html:118
+#: templates/admin/forms/entries.html:151
+msgid "Back to form"
+msgstr "Tilbake til skjemaet"
+
+#: templates/admin/forms/entries.html:120
+msgid "Export CSV"
+msgstr "Exporter CSV"
+
+#: templates/admin/forms/entries.html:123
+msgid "Entries"
+msgstr "Innlegg"
+
+#: templates/admin/forms/entries.html:152
+msgid "Delete selected"
+msgstr "Slett valgte"
+
+#: templates/admin/forms/entries.html:156
+msgid "No entries to display"
+msgstr "Ingen innlegg å vise"
+
--- /dev/null
+# Dutch Translation for stephenmcd's django-forms-builder
+# Copyright (C) 2010 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Wouter van der Graaf <wouter@dynora.nl>, 2010.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-07-16 15:08+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Fluxility <info@fluxility.nl>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: admin.py:40 fields.py:33
+msgid "Email"
+msgstr "E-mail"
+
+#: admin.py:45 models.py:59 models.py:166
+msgid "Slug"
+msgstr "Slug"
+
+#: admin.py:48
+msgid "Sites"
+msgstr "Sites"
+
+#: admin.py:177
+#, python-format
+msgid "1 entry deleted"
+msgid_plural "%(count)s entries deleted"
+msgstr[0] "1 inzending verwijderd"
+msgstr[1] "%(count)s inzendingen verwijderd"
+
+#: admin.py:181
+#| msgid "View on site"
+msgid "View Entries"
+msgstr "Bekijk op site"
+
+#: fields.py:31
+msgid "Single line text"
+msgstr "Enkele rij tekst"
+
+#: fields.py:32
+msgid "Multi line text"
+msgstr "Meerdere rijen tekst"
+
+#: fields.py:34
+msgid "Number"
+msgstr "Nummer"
+
+#: fields.py:35
+msgid "URL"
+msgstr "URL"
+
+#: fields.py:36
+msgid "Check box"
+msgstr "Aankruisvakje"
+
+#: fields.py:37
+msgid "Check boxes"
+msgstr "Aankruisvakjes"
+
+#: fields.py:38
+msgid "Drop down"
+msgstr "Uitklapveld"
+
+#: fields.py:39
+msgid "Multi select"
+msgstr "Lijstveld"
+
+#: fields.py:40
+msgid "Radio buttons"
+msgstr "Keuzerondjes"
+
+#: fields.py:41
+msgid "File upload"
+msgstr "Bestand upload"
+
+#: fields.py:42
+msgid "Date"
+msgstr "Datum"
+
+#: fields.py:43 models.py:234
+msgid "Date/time"
+msgstr "Datum/tijd"
+
+#: fields.py:44
+msgid "Date of birth"
+msgstr "Geboortedatum"
+
+#: fields.py:45
+msgid "Hidden"
+msgstr "Verborgen"
+
+#: forms.py:52 forms.py:61 forms.py:68 forms.py:77
+msgid "Nothing"
+msgstr "Niets"
+
+#: forms.py:53
+msgid "Contains"
+msgstr "Bevat"
+
+#: forms.py:54
+msgid "Doesn't contain"
+msgstr "Bevat niet"
+
+#: forms.py:55
+msgid "Equals"
+msgstr "Gelijk aan"
+
+#: forms.py:56
+msgid "Doesn't equal"
+msgstr "Niet gelijk aan"
+
+#: forms.py:62
+msgid "Equals any"
+msgstr "Gelijk aan één van"
+
+#: forms.py:63
+msgid "Doesn't equal any"
+msgstr "Niet gelijk aan één van"
+
+#: forms.py:69
+msgid "Contains any"
+msgstr "Bevat minstens één van"
+
+#: forms.py:70
+msgid "Contains all"
+msgstr "Bevat alle van"
+
+#: forms.py:71
+msgid "Doesn't contain any"
+msgstr "Bevat geen enkele van"
+
+#: forms.py:72
+msgid "Doesn't contain all"
+msgstr "Bevat niet alle van"
+
+#: forms.py:78
+msgid "Is between"
+msgstr "Is tussen"
+
+#: forms.py:276
+#| msgid "Check boxes"
+msgid "Checked"
+msgstr "Aangevinkt"
+
+#: forms.py:276
+msgid "Not checked"
+msgstr "Niet aangevinkt"
+
+#: forms.py:299 forms.py:314
+msgid "and"
+msgstr "en"
+
+#: models.py:20
+msgid "Draft"
+msgstr "Opzet"
+
+#: models.py:21
+#| msgid "Published from"
+msgid "Published"
+msgstr "Gepubliceerd"
+
+#: models.py:58
+msgid "Title"
+msgstr "Titel"
+
+#: models.py:61
+msgid "Intro"
+msgstr "Intro"
+
+#: models.py:62
+msgid "Button text"
+msgstr "Knoptekst"
+
+#: models.py:63
+msgid "Submit"
+msgstr "Verzenden"
+
+#: models.py:64
+msgid "Response"
+msgstr "Respons"
+
+#: models.py:65
+msgid "Redirect url"
+msgstr "Doorverwijs URL"
+
+#: models.py:67
+msgid "An alternate URL to redirect to after form submission"
+msgstr "Een URL om de gebruiker naar door te verwijzen nadat hij het"
+" formulier heeft verstuurd"
+
+#: models.py:68
+msgid "Status"
+msgstr "Status"
+
+#: models.py:70
+msgid "Published from"
+msgstr "Gepubliceerd vanaf"
+
+#: models.py:71
+msgid "With published selected, won't be shown until this time"
+msgstr ""
+"Met gepubliceerd geselecteerd, wordt deze niet getoond tot dit tijdstip"
+
+#: models.py:73
+msgid "Expires on"
+msgstr "Vervalt op"
+
+#: models.py:74
+msgid "With published selected, won't be shown after this time"
+msgstr ""
+"Met gepubliceerd geselecteerd, wordt deze niet getoond vanaf dit tijdstip"
+
+#: models.py:76
+msgid "Login required"
+msgstr "Inloggen vereist"
+
+#: models.py:77
+msgid "If checked, only logged in users can view the form"
+msgstr ""
+"Als aangevinkt, dan kunnen alleen ingelogde gebruikers het formulier zien"
+
+#: models.py:78
+msgid "Send email"
+msgstr "Verstuur e-mail"
+
+#: models.py:79
+msgid "If checked, the person entering the form will be sent an email"
+msgstr ""
+"Als aangevinkt, dan krijgt de persoon die het formulier heeft ingevuld een e-"
+"mail"
+
+#: models.py:80
+msgid "From address"
+msgstr "Van adres"
+
+#: models.py:81
+msgid "The address the email will be sent from"
+msgstr "Het adres waarvandaan de e-mail verzonden wordt"
+
+#: models.py:82
+msgid "Send copies to"
+msgstr "Stuur kopie naar"
+
+#: models.py:83
+msgid "One or more email addresses, separated by commas"
+msgstr "Een of meer e-mailadressen, gescheiden door komma's"
+
+#: models.py:85
+msgid "Subject"
+msgstr "Onderwerp"
+
+#: models.py:86
+msgid "Message"
+msgstr "Bericht"
+
+#: models.py:91
+msgid "Form"
+msgstr "Formulier"
+
+#: models.py:92
+msgid "Forms"
+msgstr "Formulieren"
+
+#: models.py:139
+#| msgid "View on site"
+msgid "View form on site"
+msgstr "Bekijk formulier op site"
+
+#: models.py:140
+#| msgid "Form entries"
+msgid "Filter entries"
+msgstr "Filter inzendingen"
+
+#: models.py:141
+#| msgid "Form field entries"
+msgid "View all entries"
+msgstr "Bekijk alle inzendingen"
+
+#: models.py:142
+#| msgid "Export entries"
+msgid "Export all entries"
+msgstr "Exporteer alle inzendingen"
+
+#: models.py:165
+msgid "Label"
+msgstr "Naam"
+
+#: models.py:168
+msgid "Type"
+msgstr "Type"
+
+#: models.py:169
+msgid "Required"
+msgstr "Verplicht"
+
+#: models.py:170
+msgid "Visible"
+msgstr "Zichtbaar"
+
+#: models.py:171
+msgid "Choices"
+msgstr "Keuzes"
+
+#: models.py:176
+msgid "Default value"
+msgstr "Standaardwaarde"
+
+#: models.py:178
+msgid "Placeholder Text"
+msgstr "Voorbeeldtekst"
+
+#: models.py:180
+msgid "Help text"
+msgstr "Helptekst"
+
+#: models.py:185 templates/admin/forms/entries.html:88
+msgid "Field"
+msgstr "Veld"
+
+#: models.py:186
+msgid "Fields"
+msgstr "Velden"
+
+#: models.py:237
+msgid "Form entry"
+msgstr "Formulierinzending"
+
+#: models.py:238
+msgid "Form entries"
+msgstr "Formulierinzendingen"
+
+#: models.py:252
+msgid "Form field entry"
+msgstr "Formulierveldinzending"
+
+#: models.py:253
+msgid "Form field entries"
+msgstr "Formulierveldinzendingen"
+
+#: models.py:281
+msgid "Order"
+msgstr "Volgorde"
+
+#: templates/admin/forms/change_form.html:9
+#: templates/admin/forms/entries.html:117
+#| msgid "Form entries"
+msgid "View entries"
+msgstr "Bekijk inzendingen"
+
+#: templates/admin/forms/change_form.html:12
+msgid "History"
+msgstr "Geschiedenis"
+
+#: templates/admin/forms/change_form.html:16
+msgid "View on site"
+msgstr "Bekijk op site"
+
+#: templates/admin/forms/entries.html:62
+msgid "No entries selected"
+msgstr "Geen inzendingen geselecteerd"
+
+#: templates/admin/forms/entries.html:65
+msgid "Delete selected entries?"
+msgstr "Verwijder geselecteerde inzendingen?"
+
+#: templates/admin/forms/entries.html:74
+msgid "Home"
+msgstr "Home"
+
+#: templates/admin/forms/entries.html:89
+msgid "Include"
+msgstr "Toon"
+
+#: templates/admin/forms/entries.html:90
+msgid "Filter by"
+msgstr "Filter op"
+
+#: templates/admin/forms/entries.html:108
+msgid "All"
+msgstr "Alle"
+
+#: templates/admin/forms/entries.html:116
+#: templates/admin/forms/entries.html:157
+msgid "Back to form"
+msgstr "Terug naar formulier"
+
+#: templates/admin/forms/entries.html:118
+msgid "Export CSV"
+msgstr "Exporteer CSV"
+
+#: templates/admin/forms/entries.html:120
+msgid "Export XLS"
+msgstr "Exporteer XLS"
+
+#: templates/admin/forms/entries.html:124
+#| msgid "Export entries"
+msgid "Entries"
+msgstr "Inzendingen"
+
+#: templates/admin/forms/entries.html:158
+#| msgid "Multi select"
+msgid "Delete selected"
+msgstr "Verwijder geselecteerde"
+
+#: templates/admin/forms/entries.html:162
+msgid "No entries to display"
+msgstr "Geen inzendingen om te tonen"
--- /dev/null
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pl\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-07-18 21:02+0200\n"
+"PO-Revision-Date: 2014-07-18 23:05+0100\n"
+"Last-Translator: Kamil Dębowski <poczta@kdebowski.pl>\n"
+"Language-Team: Kamil Dębowski <poczta@kdebowski.pl>\n"
+"Language: pl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
+"|| n%100>=20) ? 1 : 2);\n"
+"X-Generator: Poedit 1.6.6\n"
+
+#: .\admin.py:40 .\fields.py:33
+msgid "Email"
+msgstr "Email"
+
+#: .\admin.py:45 .\models.py:60 .\models.py:151
+msgid "Slug"
+msgstr "Slug"
+
+#: .\admin.py:48
+msgid "Sites"
+msgstr "Strony"
+
+#: .\admin.py:177
+#, python-format
+msgid "1 entry deleted"
+msgid_plural "%(count)s entries deleted"
+msgstr[0] "1 wpis usunięty"
+msgstr[1] "%(count)s wpisy usunięte"
+msgstr[2] "%(count)s wpisów usuniętych"
+
+#: .\admin.py:181
+msgid "View Entries"
+msgstr "Zobacz wpisy"
+
+#: .\fields.py:31
+msgid "Single line text"
+msgstr "Pojedynczy wiersz tekstu"
+
+#: .\fields.py:32
+msgid "Multi line text"
+msgstr "Wiele linii tekstu"
+
+#: .\fields.py:34
+msgid "Number"
+msgstr "Liczba"
+
+#: .\fields.py:35
+msgid "URL"
+msgstr "URL"
+
+#: .\fields.py:36
+msgid "Check box"
+msgstr "Pole wyboru"
+
+#: .\fields.py:37
+msgid "Check boxes"
+msgstr "Pola wyboru"
+
+#: .\fields.py:38
+msgid "Drop down"
+msgstr "Lista rozwijana"
+
+#: .\fields.py:39
+msgid "Multi select"
+msgstr "Wielokrotny wybór"
+
+#: .\fields.py:40
+msgid "Radio buttons"
+msgstr "Jednokrotny wybór"
+
+#: .\fields.py:41
+msgid "File upload"
+msgstr "Przesłanie pliku"
+
+#: .\fields.py:42
+msgid "Date"
+msgstr "Data"
+
+#: .\fields.py:43 .\models.py:219
+msgid "Date/time"
+msgstr "Data/czas"
+
+#: .\fields.py:44
+msgid "Date of birth"
+msgstr "Data urodzenia"
+
+#: .\fields.py:45
+msgid "Hidden"
+msgstr "Ukryte"
+
+#: .\forms.py:52 .\forms.py:61 .\forms.py:68 .\forms.py:77
+msgid "Nothing"
+msgstr "Nic"
+
+#: .\forms.py:53
+msgid "Contains"
+msgstr "Zawiera"
+
+#: .\forms.py:54
+msgid "Doesn't contain"
+msgstr "Nie zawiera"
+
+#: .\forms.py:55
+msgid "Equals"
+msgstr "Równy"
+
+#: .\forms.py:56
+msgid "Doesn't equal"
+msgstr "Nieówny"
+
+#: .\forms.py:62
+msgid "Equals any"
+msgstr "Równy któremukolwiek"
+
+#: .\forms.py:63
+msgid "Doesn't equal any"
+msgstr "Nierówny któremukolwiek"
+
+#: .\forms.py:69
+msgid "Contains any"
+msgstr "Zawiera jakiekolwiek"
+
+#: .\forms.py:70
+msgid "Contains all"
+msgstr "Zawiera wszystkie"
+
+#: .\forms.py:71
+msgid "Doesn't contain any"
+msgstr "Niezawiera któregokolwiek"
+
+#: .\forms.py:72
+msgid "Doesn't contain all"
+msgstr "Nie zawiera wszystkich"
+
+#: .\forms.py:78
+msgid "Is between"
+msgstr "Jest pomiędzy"
+
+#: .\forms.py:272
+msgid "Checked"
+msgstr "Zaznaczony"
+
+#: .\forms.py:272
+msgid "Not checked"
+msgstr "Niezaznaczony"
+
+#: .\forms.py:295 .\forms.py:310
+msgid "and"
+msgstr "i"
+
+#: .\models.py:21
+msgid "Draft"
+msgstr "Szkic"
+
+#: .\models.py:22
+msgid "Published"
+msgstr "Opublikowany"
+
+#: .\models.py:59
+msgid "Title"
+msgstr "Tytuł"
+
+#: .\models.py:62
+msgid "Intro"
+msgstr "Wstęp"
+
+#: .\models.py:63
+msgid "Button text"
+msgstr "Tekst przycisku"
+
+#: .\models.py:64
+msgid "Submit"
+msgstr "Wyślij"
+
+#: .\models.py:65
+msgid "Response"
+msgstr "Odpowiedź"
+
+#: .\models.py:66
+msgid "Redirect url"
+msgstr "Adres do przekierowania"
+
+#: .\models.py:68
+msgid "An alternate URL to redirect to after form submission"
+msgstr "Alternatywny adres do przekierowania po wysłaniu formularza"
+
+#: .\models.py:69
+msgid "Status"
+msgstr "Status"
+
+#: .\models.py:71
+msgid "Published from"
+msgstr "Opublikowany od"
+
+#: .\models.py:72
+msgid "With published selected, won't be shown until this time"
+msgstr "Jeśli opublikowany, nie będzie widoczny przed tym czasem"
+
+#: .\models.py:74
+msgid "Expires on"
+msgstr "Wygasa"
+
+#: .\models.py:75
+msgid "With published selected, won't be shown after this time"
+msgstr "Jeśli opublikowany, nie będzie widoczny po tym czasie"
+
+#: .\models.py:77
+msgid "Login required"
+msgstr "Wymaga logowania"
+
+#: .\models.py:78
+msgid "If checked, only logged in users can view the form"
+msgstr "Jeśli zaznaczone, tylko zalogowani użytkownicy zobaczą formularz"
+
+#: .\models.py:79
+msgid "Send email"
+msgstr "Wyślij email"
+
+#: .\models.py:80
+msgid "If checked, the person entering the form will be sent an email"
+msgstr "Jeśli zaznaczone, osoba wysyłająca formularz otrzyma email"
+
+#: .\models.py:81
+msgid "From address"
+msgstr "Z adresu"
+
+#: .\models.py:82
+msgid "The address the email will be sent from"
+msgstr "Adres, z którego będzie wysłany formularz"
+
+#: .\models.py:83
+msgid "Send copies to"
+msgstr "Wyślij kopie do"
+
+#: .\models.py:84
+msgid "One or more email addresses, separated by commas"
+msgstr "Jeden lub więcej adresów email, oddzielonych przecinkami"
+
+#: .\models.py:86
+msgid "Subject"
+msgstr "Temat"
+
+#: .\models.py:87
+msgid "Message"
+msgstr "Wiadomość"
+
+#: .\models.py:92
+msgid "Form"
+msgstr "Formularz"
+
+#: .\models.py:93
+msgid "Forms"
+msgstr "Formularze"
+
+#: .\models.py:124
+msgid "View form on site"
+msgstr "Zobacz formularz na stronie"
+
+#: .\models.py:125
+msgid "Filter entries"
+msgstr "Filtruj wpisy"
+
+#: .\models.py:126
+msgid "View all entries"
+msgstr "Zobacz wszystkie wpisy"
+
+#: .\models.py:127
+msgid "Export all entries"
+msgstr "Eksportuj wszystkie wpisy"
+
+#: .\models.py:150
+msgid "Label"
+msgstr "Etykieta"
+
+#: .\models.py:153
+msgid "Type"
+msgstr "Typ"
+
+#: .\models.py:154
+msgid "Required"
+msgstr "Wymagane"
+
+#: .\models.py:155
+msgid "Visible"
+msgstr "Widoczne"
+
+#: .\models.py:156
+msgid "Choices"
+msgstr "Opcje wyboru"
+
+#: .\models.py:161
+msgid "Default value"
+msgstr "Wartość domyślna"
+
+#: .\models.py:163
+msgid "Placeholder Text"
+msgstr "Placeholder"
+
+#: .\models.py:165
+msgid "Help text"
+msgstr "Tekst pomocniczy"
+
+#: .\models.py:170 .\templates\admin\forms\entries.html:88
+msgid "Field"
+msgstr "Pole"
+
+#: .\models.py:171
+msgid "Fields"
+msgstr "Pola"
+
+#: .\models.py:222
+msgid "Form entry"
+msgstr "Wpis formularza"
+
+#: .\models.py:223
+msgid "Form entries"
+msgstr "Wpisy formularza"
+
+#: .\models.py:237
+msgid "Form field entry"
+msgstr "Wpis pola formularza"
+
+#: .\models.py:238
+msgid "Form field entries"
+msgstr "Wpisy pola formularza"
+
+#: .\models.py:266
+msgid "Order"
+msgstr "Porządek"
+
+#: .\templates\admin\forms\change_form.html:10
+#: .\templates\admin\forms\entries.html:117
+msgid "View entries"
+msgstr "Zobacz wpisy"
+
+#: .\templates\admin\forms\change_form.html:13
+msgid "History"
+msgstr "Historia"
+
+#: .\templates\admin\forms\change_form.html:17
+msgid "View on site"
+msgstr "Zobacz na stronie"
+
+#: .\templates\admin\forms\entries.html:62
+msgid "No entries selected"
+msgstr "Brak zaznaczonych wpisów"
+
+#: .\templates\admin\forms\entries.html:65
+msgid "Delete selected entries?"
+msgstr "Usunąć zaznaczone wpisy?"
+
+#: .\templates\admin\forms\entries.html:74
+msgid "Home"
+msgstr "Strona główna"
+
+#: .\templates\admin\forms\entries.html:89
+msgid "Include"
+msgstr "Uwzględnij"
+
+#: .\templates\admin\forms\entries.html:90
+msgid "Filter by"
+msgstr "Filtruj używając"
+
+#: .\templates\admin\forms\entries.html:108
+msgid "All"
+msgstr "Wszystko"
+
+#: .\templates\admin\forms\entries.html:116
+#: .\templates\admin\forms\entries.html:157
+msgid "Back to form"
+msgstr "Wróć do formlarza"
+
+#: .\templates\admin\forms\entries.html:118
+msgid "Export CSV"
+msgstr "Eksportuj do CSV"
+
+#: .\templates\admin\forms\entries.html:120
+msgid "Export XLS"
+msgstr "Eksportuj do XLS"
+
+#: .\templates\admin\forms\entries.html:124
+msgid "Entries"
+msgstr "Wpisy"
+
+#: .\templates\admin\forms\entries.html:158
+msgid "Delete selected"
+msgstr "Usuń zaznaczone"
+
+#: .\templates\admin\forms\entries.html:162
+msgid "No entries to display"
+msgstr "Brak wpisów do wyświetlenia"
--- /dev/null
+# Brazilian portuguese translation for stephenmcd's django-forms-builder
+# This file is distributed under the same license as the PACKAGE package.
+# Tiago Myhro Ilieve <github@myhro.info>, 2013.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: django-forms-builder\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-04-23 23:35-0300\n"
+"PO-Revision-Date: 2013-05-03 00:10-0300\n"
+"Last-Translator: Tiago Myhro Ilieve <github@myhro.info>\n"
+"Language-Team: \n"
+"Language: pt-BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+#: admin.py:37 fields.py:32
+msgid "Email"
+msgstr "E-mail"
+
+#: admin.py:42 models.py:55 models.py:142
+msgid "Slug"
+msgstr "Slug"
+
+#: admin.py:45
+msgid "Sites"
+msgstr "Sites"
+
+#: admin.py:168
+#, python-format
+msgid "1 entry deleted"
+msgid_plural "%(count)s entries deleted"
+msgstr[0] "1 registro apagado"
+msgstr[1] "%(count)s registros apagados"
+
+#: admin.py:172
+msgid "View Entries"
+msgstr "Ver Registros"
+
+#: fields.py:30
+msgid "Single line text"
+msgstr "Texto de uma linha"
+
+#: fields.py:31
+msgid "Multi line text"
+msgstr "Texto de múltiplas linhas"
+
+#: fields.py:33
+msgid "Number"
+msgstr "Número"
+
+#: fields.py:34
+msgid "URL"
+msgstr "URL"
+
+#: fields.py:35
+msgid "Check box"
+msgstr "Caixa de seleção"
+
+#: fields.py:36
+msgid "Check boxes"
+msgstr "Caixas de seleção"
+
+#: fields.py:37
+msgid "Drop down"
+msgstr "Menu drop down"
+
+#: fields.py:38
+msgid "Multi select"
+msgstr "Caixa de listagem"
+
+#: fields.py:39
+msgid "Radio buttons"
+msgstr "Botões de opção"
+
+#: fields.py:40
+msgid "File upload"
+msgstr "Envio de arquivo"
+
+#: fields.py:41
+msgid "Date"
+msgstr "Data"
+
+#: fields.py:42 models.py:210
+msgid "Date/time"
+msgstr "Data/hora"
+
+#: fields.py:43
+msgid "Date of birth"
+msgstr "Data de nascimento"
+
+#: fields.py:44
+msgid "Hidden"
+msgstr "Oculto"
+
+#: forms.py:50 forms.py:59 forms.py:66 forms.py:75
+msgid "Nothing"
+msgstr "Nada"
+
+#: forms.py:51
+msgid "Contains"
+msgstr "Contém"
+
+#: forms.py:52
+msgid "Doesn't contain"
+msgstr "Não contém"
+
+#: forms.py:53
+msgid "Equals"
+msgstr "Igual"
+
+#: forms.py:54
+msgid "Doesn't equal"
+msgstr "Diferente"
+
+#: forms.py:60
+msgid "Equals any"
+msgstr "Igual algum"
+
+#: forms.py:61
+msgid "Doesn't equal any"
+msgstr "Diferente de algum"
+
+#: forms.py:67
+msgid "Contains any"
+msgstr "Contém algum"
+
+#: forms.py:68
+msgid "Contains all"
+msgstr "Contém todos"
+
+#: forms.py:69
+msgid "Doesn't contain any"
+msgstr "Não contém nenhum"
+
+#: forms.py:70
+msgid "Doesn't contain all"
+msgstr "Não contém todos"
+
+#: forms.py:76
+msgid "Is between"
+msgstr "Está entre"
+
+#: forms.py:270
+msgid "Checked"
+msgstr "Selecionado"
+
+#: forms.py:270
+msgid "Not checked"
+msgstr "Não selecionado"
+
+#: forms.py:293 forms.py:308
+msgid "and"
+msgstr "e"
+
+#: models.py:17
+msgid "Draft"
+msgstr "Rascunho"
+
+#: models.py:18
+msgid "Published"
+msgstr "Publicado"
+
+#: models.py:54
+msgid "Title"
+msgstr "Título"
+
+#: models.py:57
+msgid "Intro"
+msgstr "Introdução"
+
+#: models.py:58
+msgid "Button text"
+msgstr "Texto do botão"
+
+#: models.py:59
+msgid "Submit"
+msgstr "Enviar"
+
+#: models.py:60
+msgid "Response"
+msgstr "Resposta"
+
+#: models.py:61
+msgid "Status"
+msgstr "Status"
+
+#: models.py:63
+msgid "Published from"
+msgstr "Publicado em"
+
+#: models.py:64
+msgid "With published selected, won't be shown until this time"
+msgstr "Com a opção publicado selecionada, não será exibido antes desta data/hora"
+
+#: models.py:66
+msgid "Expires on"
+msgstr "Expira em"
+
+#: models.py:67
+msgid "With published selected, won't be shown after this time"
+msgstr "Com a opção publicado selecionada, não será exibido após esta data/hora"
+
+#: models.py:69
+msgid "Login required"
+msgstr "Login necessário"
+
+#: models.py:70
+msgid "If checked, only logged in users can view the form"
+msgstr "Se selecionado, somente usuários logados poderão ver o formulário"
+
+#: models.py:71
+msgid "Send email"
+msgstr "Enviar e-mail"
+
+#: models.py:72
+msgid "If checked, the person entering the form will be sent an email"
+msgstr "Se selecionado, a pessoa receberá um e-mail quando o formulário for submetido"
+
+#: models.py:73
+msgid "From address"
+msgstr "Endereço do remetente"
+
+#: models.py:74
+msgid "The address the email will be sent from"
+msgstr "Endereço de e-mail a partir do qual será enviado"
+
+#: models.py:75
+msgid "Send copies to"
+msgstr "Enviar cópias para"
+
+#: models.py:76
+msgid "One or more email addresses, separated by commas"
+msgstr "Um ou mais endereços de e-mail, separados por vírgulas"
+
+#: models.py:78
+msgid "Subject"
+msgstr "Assunto"
+
+#: models.py:79
+msgid "Message"
+msgstr "Mensagem"
+
+#: models.py:84
+msgid "Form"
+msgstr "Formulário"
+
+#: models.py:85
+msgid "Forms"
+msgstr "Formulários"
+
+#: models.py:116
+msgid "View form on site"
+msgstr "Visualizar formulário"
+
+#: models.py:117
+msgid "Filter entries"
+msgstr "Filtrar entradas"
+
+#: models.py:118
+msgid "View all entries"
+msgstr "Ver todas as entradas"
+
+#: models.py:119
+msgid "Export all entries"
+msgstr "Exportar todas as entradas"
+
+#: models.py:141
+msgid "Label"
+msgstr "Legenda"
+
+#: models.py:144
+msgid "Type"
+msgstr "Tipo"
+
+#: models.py:145
+msgid "Required"
+msgstr "Exigido"
+
+#: models.py:146
+msgid "Visible"
+msgstr "Visível"
+
+#: models.py:147
+msgid "Choices"
+msgstr "Opções"
+
+#: models.py:152
+msgid "Default value"
+msgstr "Valor padrão"
+
+#: models.py:154
+msgid "Placeholder Text"
+msgstr "Texto do placeholder"
+
+#: models.py:156
+msgid "Help text"
+msgstr "Texto de ajuda"
+
+#: models.py:161 templates/admin/forms/entries.html:87
+msgid "Field"
+msgstr "Campo"
+
+#: models.py:162
+msgid "Fields"
+msgstr "Campos"
+
+#: models.py:213
+msgid "Form entry"
+msgstr "Entrada do formulário"
+
+#: models.py:214
+msgid "Form entries"
+msgstr "Entradas do formulário"
+
+#: models.py:228
+msgid "Form field entry"
+msgstr "Entrada do campo do formulário"
+
+#: models.py:229
+msgid "Form field entries"
+msgstr "Entradas do campo do formulário"
+
+#: models.py:257
+msgid "Order"
+msgstr "Ordem"
+
+#: templates/admin/forms/change_form.html:10
+#: templates/admin/forms/entries.html:116
+msgid "View entries"
+msgstr "Ver entradas"
+
+#: templates/admin/forms/change_form.html:13
+msgid "History"
+msgstr "Histórico"
+
+#: templates/admin/forms/change_form.html:17
+msgid "View on site"
+msgstr "Ver no site"
+
+#: templates/admin/forms/entries.html:61
+msgid "No entries selected"
+msgstr "Não há entradas selecionadas"
+
+#: templates/admin/forms/entries.html:64
+msgid "Delete selected entries?"
+msgstr "Apagar as entradas selecionadas?"
+
+#: templates/admin/forms/entries.html:73
+msgid "Home"
+msgstr "Home"
+
+#: templates/admin/forms/entries.html:88
+msgid "Include"
+msgstr "Incluir"
+
+#: templates/admin/forms/entries.html:89
+msgid "Filter by"
+msgstr "Filtrar por"
+
+#: templates/admin/forms/entries.html:107
+msgid "All"
+msgstr "Todos"
+
+#: templates/admin/forms/entries.html:115
+#: templates/admin/forms/entries.html:158
+msgid "Back to form"
+msgstr "Voltar para o formulário"
+
+#: templates/admin/forms/entries.html:117
+msgid "Export CSV"
+msgstr "Exportar CSV"
+
+#: templates/admin/forms/entries.html:119
+msgid "Export XLS"
+msgstr "Exportar XLS"
+
+#: templates/admin/forms/entries.html:123
+msgid "Entries"
+msgstr "Entradas"
+
+#: templates/admin/forms/entries.html:159
+msgid "Delete selected"
+msgstr "Apagar selecionado"
+
+#: templates/admin/forms/entries.html:163
+msgid "No entries to display"
+msgstr "Não há entradas para exibir"
--- /dev/null
+# Russian Translation for stephenmcd's django-forms-builder
+# Copyright (C) 2012 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Fill Q <admin@njoyx.net>, 2012.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-09-06 19:08+0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Fill Q <admin@njoyx.net>\n"
+"Language-Team: RU LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#: admin.py:36 fields.py:30
+msgid "Email"
+msgstr "Емайл"
+
+#: admin.py:41 models.py:55 models.py:142
+msgid "Slug"
+msgstr "Краткое имя(slug)"
+
+#: admin.py:44
+msgid "Sites"
+msgstr "Сайты"
+
+#: admin.py:158
+#, python-format
+msgid "1 entry deleted"
+msgid_plural "%(count)s entries deleted"
+msgstr[0] "1 запись удалена"
+msgstr[1] "%(count)s записи удалено"
+msgstr[2] "%(count)s записей удалено"
+
+#: admin.py:162
+#, fuzzy
+msgid "View Entries"
+msgstr "Показать данные"
+
+#: fields.py:28
+msgid "Single line text"
+msgstr "Однострочный текст"
+
+#: fields.py:29
+msgid "Multi line text"
+msgstr "Многострочный текст"
+
+#: fields.py:31
+msgid "Number"
+msgstr "Номер"
+
+#: fields.py:32
+msgid "URL"
+msgstr "URL"
+
+#: fields.py:33
+msgid "Check box"
+msgstr "Чекбокс"
+
+#: fields.py:34
+msgid "Check boxes"
+msgstr "Чекбоксы"
+
+#: fields.py:35
+msgid "Drop down"
+msgstr "Выпадающее меню"
+
+#: fields.py:36
+msgid "Multi select"
+msgstr "Мультивыбор"
+
+#: fields.py:37
+msgid "Radio buttons"
+msgstr "Радио-кнопки"
+
+#: fields.py:38
+msgid "File upload"
+msgstr "Загрузка файлов"
+
+#: fields.py:39
+msgid "Date"
+msgstr "Дата"
+
+#: fields.py:40 models.py:210
+msgid "Date/time"
+msgstr "Дата/время"
+
+#: fields.py:41
+msgid "Date of birth"
+msgstr "Дата рождения"
+
+#: fields.py:42
+msgid "Hidden"
+msgstr "Скрыто"
+
+#: forms.py:30 forms.py:38 forms.py:44
+msgid "Nothing"
+msgstr "Ничего"
+
+#: forms.py:31
+msgid "Contains"
+msgstr "Содержит"
+
+#: forms.py:32
+msgid "Doesn't contain"
+msgstr "Не содержит"
+
+#: forms.py:33 forms.py:39
+msgid "Equals"
+msgstr "Равняется"
+
+#: forms.py:34 forms.py:40
+msgid "Doesn't equal"
+msgstr "Не равняется"
+
+#: forms.py:45
+msgid "Is between"
+msgstr "Между"
+
+#: forms.py:218
+msgid "Checked"
+msgstr "Выбрано"
+
+#: forms.py:218
+msgid "Not checked"
+msgstr "Не выбрано"
+
+#: forms.py:232 forms.py:247
+msgid "and"
+msgstr "и"
+
+#: models.py:17
+msgid "Draft"
+msgstr "Черновик"
+
+#: models.py:18
+msgid "Published"
+msgstr "Опубликовано"
+
+#: models.py:54
+msgid "Title"
+msgstr "Название"
+
+#: models.py:57
+msgid "Intro"
+msgstr "Вступление"
+
+#: models.py:58
+msgid "Button text"
+msgstr "Текст кнопки"
+
+#: models.py:59
+msgid "Submit"
+msgstr "Отправить"
+
+#: models.py:60
+msgid "Response"
+msgstr "Ответ"
+
+#: models.py:61
+msgid "Status"
+msgstr "Статус"
+
+#: models.py:63
+msgid "Published from"
+msgstr "Опубликовано с"
+
+#: models.py:64
+msgid "With published selected, won't be shown until this time"
+msgstr ""
+"Форма не будет показана до даты/времени которое указано в публикации(если "
+"отмечено \"опубликовать\")."
+
+#: models.py:66
+msgid "Expires on"
+msgstr "Действительно до"
+
+#: models.py:67
+msgid "With published selected, won't be shown after this time"
+msgstr ""
+"Форма не будет показана после даты/времени которое указано в публикации(если "
+"отмечено \"опубликовать\")."
+
+#: models.py:69
+msgid "Login required"
+msgstr "Требуется авторизация"
+
+#: models.py:70
+msgid "If checked, only logged in users can view the form"
+msgstr ""
+"Если отмечено, только зарегестрированные пользователи могут видеть форму"
+
+#: models.py:71
+msgid "Send email"
+msgstr "Отослать емайл"
+
+#: models.py:72
+msgid "If checked, the person entering the form will be sent an email"
+msgstr "Если выбрано, данные формы будут отосланы на емайл"
+
+#: models.py:73
+msgid "From address"
+msgstr "С адреса"
+
+#: models.py:74
+msgid "The address the email will be sent from"
+msgstr "Адрес емайла с которого будет отослано письмо"
+
+#: models.py:75
+msgid "Send copies to"
+msgstr "Отправить копию по адресу"
+
+#: models.py:76
+msgid "One or more email addresses, separated by commas"
+msgstr "Одно или более емайлов, разделенных запятой"
+
+#: models.py:78
+msgid "Subject"
+msgstr "Тема"
+
+#: models.py:79
+msgid "Message"
+msgstr "Сообщение"
+
+#: models.py:84
+msgid "Form"
+msgstr "Форму"
+
+#: models.py:85
+msgid "Forms"
+msgstr "Формы"
+
+#: models.py:116
+msgid "View form on site"
+msgstr "Показать форму на сайте"
+
+#: models.py:117
+msgid "Filter entries"
+msgstr "Данные формы"
+
+#: models.py:118
+msgid "View all entries"
+msgstr "Показать все данные"
+
+#: models.py:119
+msgid "Export all entries"
+msgstr "Экспорт всех данных"
+
+#: models.py:141
+msgid "Label"
+msgstr "Метка"
+
+#: models.py:144
+msgid "Type"
+msgstr "Тип"
+
+#: models.py:145
+msgid "Required"
+msgstr "Обязательное"
+
+#: models.py:146
+msgid "Visible"
+msgstr "Видимое"
+
+#: models.py:147
+msgid "Choices"
+msgstr "Выбор"
+
+#: models.py:152
+msgid "Default value"
+msgstr "Стандартное значение"
+
+#: models.py:154
+msgid "Placeholder Text"
+msgstr "Текст заполнителя"
+
+#: models.py:156
+msgid "Help text"
+msgstr "Текст помощи"
+
+#: models.py:161 templates/admin/forms/entries.html:87
+msgid "Field"
+msgstr "Поле"
+
+#: models.py:162
+msgid "Fields"
+msgstr "Поля"
+
+#: models.py:213
+msgid "Form entry"
+msgstr "Данные формы"
+
+#: models.py:214
+msgid "Form entries"
+msgstr "Данные формы"
+
+#: models.py:228
+msgid "Form field entry"
+msgstr "Данные поля формы"
+
+#: models.py:229
+msgid "Form field entries"
+msgstr "Данные поля формы"
+
+#: models.py:257
+msgid "Order"
+msgstr "Сортировка"
+
+#: templates/admin/forms/change_form.html:10
+#: templates/admin/forms/entries.html:116
+msgid "View entries"
+msgstr "Показать данные формы"
+
+#: templates/admin/forms/change_form.html:13
+msgid "History"
+msgstr "История"
+
+#: templates/admin/forms/change_form.html:17
+msgid "View on site"
+msgstr "Показать на сайте"
+
+#: templates/admin/forms/entries.html:61
+msgid "No entries selected"
+msgstr "Нет выбранных записей"
+
+#: templates/admin/forms/entries.html:64
+msgid "Delete selected entries?"
+msgstr "Удалить выбранные записи?"
+
+#: templates/admin/forms/entries.html:73
+msgid "Home"
+msgstr "Домой"
+
+#: templates/admin/forms/entries.html:88
+msgid "Include"
+msgstr "Содержит"
+
+#: templates/admin/forms/entries.html:89
+msgid "Filter by"
+msgstr "Фильтровать по"
+
+#: templates/admin/forms/entries.html:107
+msgid "All"
+msgstr "Все"
+
+#: templates/admin/forms/entries.html:115
+#: templates/admin/forms/entries.html:158
+msgid "Back to form"
+msgstr "Вернуться к форме"
+
+#: templates/admin/forms/entries.html:117
+msgid "Export CSV"
+msgstr "Экспортировать в CSV"
+
+#: templates/admin/forms/entries.html:119
+msgid "Export XLS"
+msgstr "Экспортировать в XLS"
+
+#: templates/admin/forms/entries.html:123
+#, fuzzy
+msgid "Entries"
+msgstr "Данные"
+
+#: templates/admin/forms/entries.html:159
+#, fuzzy
+msgid "Delete selected"
+msgstr "Удалить выбранные"
+
+#: templates/admin/forms/entries.html:163
+msgid "No entries to display"
+msgstr "Нет данных для показа"
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+from forms_builder.forms import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('sites', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Field',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('label', models.CharField(max_length=200, verbose_name='Label')),
+ ('slug', models.SlugField(default='', max_length=100, verbose_name='Slug', blank=True)),
+ ('field_type', models.IntegerField(verbose_name='Type', choices=[(1, 'Single line text'), (2, 'Multi line text'), (3, 'Email'), (13, 'Number'), (14, 'URL'), (4, 'Check box'), (5, 'Check boxes'), (6, 'Drop down'), (7, 'Multi select'), (8, 'Radio buttons'), (9, 'File upload'), (10, 'Date'), (11, 'Date/time'), (15, 'Date of birth'), (12, 'Hidden')])),
+ ('required', models.BooleanField(default=True, verbose_name='Required')),
+ ('visible', models.BooleanField(default=True, verbose_name='Visible')),
+ ('choices', models.CharField(help_text='Comma separated options where applicable. If an option itself contains commas, surround the option starting with the `character and ending with the ` character.', max_length=1000, verbose_name='Choices', blank=True)),
+ ('default', models.CharField(max_length=2000, verbose_name='Default value', blank=True)),
+ ('placeholder_text', models.CharField(max_length=100, null=True, verbose_name='Placeholder Text', blank=True)),
+ ('help_text', models.CharField(max_length=100, verbose_name='Help text', blank=True)),
+ ('order', models.IntegerField(null=True, verbose_name='Order', blank=True)),
+ ],
+ options={
+ 'ordering': ('order',),
+ 'abstract': False,
+ 'verbose_name': 'Field',
+ 'verbose_name_plural': 'Fields',
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='FieldEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('field_id', models.IntegerField()),
+ ('value', models.CharField(max_length=2000, null=True)),
+ ],
+ options={
+ 'abstract': False,
+ 'verbose_name': 'Form field entry',
+ 'verbose_name_plural': 'Form field entries',
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Form',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('title', models.CharField(max_length=50, verbose_name='Title')),
+ ('slug', models.SlugField(verbose_name='Slug', unique=True, max_length=100, editable=False)),
+ ('intro', models.TextField(verbose_name='Intro', blank=True)),
+ ('button_text', models.CharField(default='Submit', max_length=50, verbose_name='Button text')),
+ ('response', models.TextField(verbose_name='Response', blank=True)),
+ ('redirect_url', models.CharField(help_text='An alternate URL to redirect to after form submission', max_length=200, null=True, verbose_name='Redirect url', blank=True)),
+ ('status', models.IntegerField(default=2, verbose_name='Status', choices=[(1, 'Draft'), (2, 'Published')])),
+ ('publish_date', models.DateTimeField(help_text="With published selected, won't be shown until this time", null=True, verbose_name='Published from', blank=True)),
+ ('expiry_date', models.DateTimeField(help_text="With published selected, won't be shown after this time", null=True, verbose_name='Expires on', blank=True)),
+ ('login_required', models.BooleanField(default=False, help_text='If checked, only logged in users can view the form', verbose_name='Login required')),
+ ('send_email', models.BooleanField(default=True, help_text='If checked, the person entering the form will be sent an email', verbose_name='Send email')),
+ ('email_from', models.EmailField(help_text='The address the email will be sent from', max_length=75, verbose_name='From address', blank=True)),
+ ('email_copies', models.CharField(help_text='One or more email addresses, separated by commas', max_length=200, verbose_name='Send copies to', blank=True)),
+ ('email_subject', models.CharField(max_length=200, verbose_name='Subject', blank=True)),
+ ('email_message', models.TextField(verbose_name='Message', blank=True)),
+ ('sites', models.ManyToManyField(default=[settings.SITE_ID], related_name='forms_form_forms', to='sites.Site')),
+ ],
+ options={
+ 'abstract': False,
+ 'verbose_name': 'Form',
+ 'verbose_name_plural': 'Forms',
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='FormEntry',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('entry_time', models.DateTimeField(verbose_name='Date/time')),
+ ('form', models.ForeignKey(related_name='entries', to='forms.Form')),
+ ],
+ options={
+ 'abstract': False,
+ 'verbose_name': 'Form entry',
+ 'verbose_name_plural': 'Form entries',
+ },
+ bases=(models.Model,),
+ ),
+ migrations.AddField(
+ model_name='fieldentry',
+ name='entry',
+ field=models.ForeignKey(related_name='fields', to='forms.FormEntry'),
+ preserve_default=True,
+ ),
+ migrations.AddField(
+ model_name='field',
+ name='form',
+ field=models.ForeignKey(related_name='fields', to='forms.Form'),
+ preserve_default=True,
+ ),
+ ]
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('forms', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='form',
+ name='email_from',
+ field=models.EmailField(max_length=254, verbose_name='From address', blank=True, help_text='The address the email will be sent from'),
+ ),
+ ]
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('forms', '0002_auto_20150819_1046'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='field',
+ name='label',
+ field=models.CharField(verbose_name='Label', max_length=2048),
+ ),
+ migrations.AlterField(
+ model_name='form',
+ name='slug',
+ field=models.SlugField(unique=True, verbose_name='Slug', max_length=100),
+ ),
+ ]
--- /dev/null
+from __future__ import unicode_literals
+
+from django.contrib.sites.models import Site
+from django.core.exceptions import ValidationError
+from django.core.urlresolvers import reverse
+from django.db import models
+from django.db.models import Q
+from django.utils.encoding import python_2_unicode_compatible
+from django.utils.translation import ugettext, ugettext_lazy as _
+from future.builtins import str
+
+from forms_builder.forms import fields
+from forms_builder.forms import settings
+from forms_builder.forms.utils import now, slugify, unique_slug
+
+
+STATUS_DRAFT = 1
+STATUS_PUBLISHED = 2
+STATUS_CHOICES = (
+ (STATUS_DRAFT, _("Draft")),
+ (STATUS_PUBLISHED, _("Published")),
+)
+
+
+class FormManager(models.Manager):
+ """
+ Only show published forms for non-staff users.
+ """
+ def published(self, for_user=None):
+ if for_user is not None and for_user.is_staff:
+ return self.all()
+ filters = [
+ Q(publish_date__lte=now()) | Q(publish_date__isnull=True),
+ Q(expiry_date__gte=now()) | Q(expiry_date__isnull=True),
+ Q(status=STATUS_PUBLISHED),
+ ]
+ if settings.USE_SITES:
+ filters.append(Q(sites=Site.objects.get_current()))
+ return self.filter(*filters)
+
+
+######################################################################
+# #
+# Each of the models are implemented as abstract to allow for #
+# subclassing. Default concrete implementations are then defined #
+# at the end of this module. #
+# #
+######################################################################
+
+@python_2_unicode_compatible
+class AbstractForm(models.Model):
+ """
+ A user-built form.
+ """
+
+ sites = models.ManyToManyField(Site,
+ default=[settings.SITE_ID], related_name="%(app_label)s_%(class)s_forms")
+ title = models.CharField(_("Title"), max_length=50)
+ slug = models.SlugField(_("Slug"), editable=settings.EDITABLE_SLUGS,
+ max_length=100, unique=True)
+ intro = models.TextField(_("Intro"), blank=True)
+ button_text = models.CharField(_("Button text"), max_length=50,
+ default=_("Submit"))
+ response = models.TextField(_("Response"), blank=True)
+ redirect_url = models.CharField(_("Redirect url"), max_length=200,
+ null=True, blank=True,
+ help_text=_("An alternate URL to redirect to after form submission"))
+ status = models.IntegerField(_("Status"), choices=STATUS_CHOICES,
+ default=STATUS_PUBLISHED)
+ publish_date = models.DateTimeField(_("Published from"),
+ help_text=_("With published selected, won't be shown until this time"),
+ blank=True, null=True)
+ expiry_date = models.DateTimeField(_("Expires on"),
+ help_text=_("With published selected, won't be shown after this time"),
+ blank=True, null=True)
+ login_required = models.BooleanField(_("Login required"), default=False,
+ help_text=_("If checked, only logged in users can view the form"))
+ send_email = models.BooleanField(_("Send email"), default=True, help_text=
+ _("If checked, the person entering the form will be sent an email"))
+ email_from = models.EmailField(_("From address"), blank=True,
+ help_text=_("The address the email will be sent from"))
+ email_copies = models.CharField(_("Send copies to"), blank=True,
+ help_text=_("One or more email addresses, separated by commas"),
+ max_length=200)
+ email_subject = models.CharField(_("Subject"), max_length=200, blank=True)
+ email_message = models.TextField(_("Message"), blank=True)
+
+ objects = FormManager()
+
+ class Meta:
+ verbose_name = _("Form")
+ verbose_name_plural = _("Forms")
+ abstract = True
+
+ def __str__(self):
+ return str(self.title)
+
+ def save(self, *args, **kwargs):
+ """
+ Create a unique slug from title - append an index and increment if it
+ already exists.
+ """
+ if not self.slug:
+ slug = slugify(self)
+ self.slug = unique_slug(self.__class__.objects, "slug", slug)
+ super(AbstractForm, self).save(*args, **kwargs)
+
+ def published(self, for_user=None):
+ """
+ Mimics the queryset logic in ``FormManager.published``, so we
+ can check a form is published when it wasn't loaded via the
+ queryset's ``published`` method, and is passed to the
+ ``render_built_form`` template tag.
+ """
+ if for_user is not None and for_user.is_staff:
+ return True
+ status = self.status == STATUS_PUBLISHED
+ publish_date = self.publish_date is None or self.publish_date <= now()
+ expiry_date = self.expiry_date is None or self.expiry_date >= now()
+ authenticated = for_user is not None and for_user.is_authenticated()
+ login_required = (not self.login_required or authenticated)
+ return status and publish_date and expiry_date and login_required
+
+ def total_entries(self):
+ """
+ Called by the admin list view where the queryset is annotated
+ with the number of entries.
+ """
+ return self.total_entries
+ total_entries.admin_order_field = "total_entries"
+
+ @models.permalink
+ def get_absolute_url(self):
+ return ("form_detail", (), {"slug": self.slug})
+
+ def admin_links(self):
+ kw = {"args": (self.id,)}
+ links = [
+ (_("View form on site"), self.get_absolute_url()),
+ (_("Filter entries"), reverse("admin:form_entries", **kw)),
+ (_("View all entries"), reverse("admin:form_entries_show", **kw)),
+ (_("Export all entries"), reverse("admin:form_entries_export", **kw)),
+ ]
+ for i, (text, url) in enumerate(links):
+ links[i] = "<a href='%s'>%s</a>" % (url, ugettext(text))
+ return "<br>".join(links)
+ admin_links.allow_tags = True
+ admin_links.short_description = ""
+
+
+class FieldManager(models.Manager):
+ """
+ Only show visible fields when displaying actual form..
+ """
+ def visible(self):
+ return self.filter(visible=True)
+
+
+@python_2_unicode_compatible
+class AbstractField(models.Model):
+ """
+ A field for a user-built form.
+ """
+
+ label = models.CharField(_("Label"), max_length=settings.LABEL_MAX_LENGTH)
+ slug = models.SlugField(_('Slug'), max_length=100, blank=True,
+ default="")
+ field_type = models.IntegerField(_("Type"), choices=fields.NAMES)
+ required = models.BooleanField(_("Required"), default=True)
+ visible = models.BooleanField(_("Visible"), default=True)
+ choices = models.CharField(_("Choices"), max_length=settings.CHOICES_MAX_LENGTH, blank=True,
+ help_text="Comma separated options where applicable. If an option "
+ "itself contains commas, surround the option starting with the %s"
+ "character and ending with the %s character." %
+ (settings.CHOICES_QUOTE, settings.CHOICES_UNQUOTE))
+ default = models.CharField(_("Default value"), blank=True,
+ max_length=settings.FIELD_MAX_LENGTH)
+ placeholder_text = models.CharField(_("Placeholder Text"), null=True,
+ blank=True, max_length=100, editable=settings.USE_HTML5)
+ help_text = models.CharField(_("Help text"), blank=True, max_length=settings.HELPTEXT_MAX_LENGTH)
+
+ objects = FieldManager()
+
+ class Meta:
+ verbose_name = _("Field")
+ verbose_name_plural = _("Fields")
+ abstract = True
+
+ def __str__(self):
+ return str(self.label)
+
+ def get_choices(self):
+ """
+ Parse a comma separated choice string into a list of choices taking
+ into account quoted choices using the ``settings.CHOICES_QUOTE`` and
+ ``settings.CHOICES_UNQUOTE`` settings.
+ """
+ choice = ""
+ quoted = False
+ for char in self.choices:
+ if not quoted and char == settings.CHOICES_QUOTE:
+ quoted = True
+ elif quoted and char == settings.CHOICES_UNQUOTE:
+ quoted = False
+ elif char == "," and not quoted:
+ choice = choice.strip()
+ if choice:
+ yield choice, choice
+ choice = ""
+ else:
+ choice += char
+ choice = choice.strip()
+ if choice:
+ yield choice, choice
+
+ def save(self, *args, **kwargs):
+ if not self.slug:
+ slug = slugify(self).replace('-', '_')
+ self.slug = unique_slug(self.form.fields, "slug", slug)
+ return super(AbstractField, self).save(*args, **kwargs)
+
+ def is_a(self, *args):
+ """
+ Helper that returns True if the field's type is given in any arg.
+ """
+ return self.field_type in args
+
+
+class AbstractFormEntry(models.Model):
+ """
+ An entry submitted via a user-built form.
+ """
+
+ entry_time = models.DateTimeField(_("Date/time"))
+
+ class Meta:
+ verbose_name = _("Form entry")
+ verbose_name_plural = _("Form entries")
+ abstract = True
+
+
+class AbstractFieldEntry(models.Model):
+ """
+ A single field value for a form entry submitted via a user-built form.
+ """
+
+ field_id = models.IntegerField()
+ value = models.CharField(max_length=settings.FIELD_MAX_LENGTH,
+ null=True)
+
+ class Meta:
+ verbose_name = _("Form field entry")
+ verbose_name_plural = _("Form field entries")
+ abstract = True
+
+
+###################################################
+# #
+# Default concrete implementations are below. #
+# #
+###################################################
+
+class FormEntry(AbstractFormEntry):
+ form = models.ForeignKey("Form", related_name="entries")
+
+
+class FieldEntry(AbstractFieldEntry):
+ entry = models.ForeignKey("FormEntry", related_name="fields")
+
+
+class Form(AbstractForm):
+ pass
+
+
+class Field(AbstractField):
+ """
+ Implements automated field ordering.
+ """
+
+ form = models.ForeignKey("Form", related_name="fields")
+ order = models.IntegerField(_("Order"), null=True, blank=True)
+
+ class Meta(AbstractField.Meta):
+ ordering = ("order",)
+
+ def save(self, *args, **kwargs):
+ if self.order is None:
+ self.order = self.form.fields.count()
+ super(Field, self).save(*args, **kwargs)
+
+ def delete(self, *args, **kwargs):
+ fields_after = self.form.fields.filter(order__gte=self.order)
+ fields_after.update(order=models.F("order") - 1)
+ super(Field, self).delete(*args, **kwargs)
--- /dev/null
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+
+
+if not (getattr(settings, "SITE_ID") and
+ "django.contrib.sites" in settings.INSTALLED_APPS):
+ raise ImproperlyConfigured("django.contrib.sites is required")
+
+
+# The maximum allowed length for field values.
+FIELD_MAX_LENGTH = getattr(settings, "FORMS_BUILDER_FIELD_MAX_LENGTH", 2000)
+
+# The maximum allowed length for field labels.
+LABEL_MAX_LENGTH = getattr(settings, "FORMS_BUILDER_LABEL_MAX_LENGTH", 200)
+
+# Sequence of custom fields that will be added to the form field types.
+EXTRA_FIELDS = getattr(settings, "FORMS_BUILDER_EXTRA_FIELDS", ())
+
+# Sequence of custom widgets that will add/update form fields widgets.
+EXTRA_WIDGETS = getattr(settings, "FORMS_BUILDER_EXTRA_WIDGETS", ())
+
+# The absolute path where files will be uploaded to.
+UPLOAD_ROOT = getattr(settings, "FORMS_BUILDER_UPLOAD_ROOT", None)
+
+# Boolean controlling whether HTML5 form fields are used.
+USE_HTML5 = getattr(settings, "FORMS_BUILDER_USE_HTML5", True)
+
+# Boolean controlling whether forms are associated to Django's Sites framework.
+USE_SITES = getattr(settings, "FORMS_BUILDER_USE_SITES", False)
+
+# Boolean controlling whether form slugs are editable in the admin.
+EDITABLE_SLUGS = getattr(settings, "FORMS_BUILDER_EDITABLE_SLUGS", False)
+
+# Char to start a quoted choice with.
+CHOICES_QUOTE = getattr(settings, "FORMS_BUILDER_CHOICES_QUOTE", "`")
+
+# Char to end a quoted choice with.
+CHOICES_UNQUOTE = getattr(settings, "FORMS_BUILDER_CHOICES_UNQUOTE", "`")
+
+# Char to use as a field delimiter when exporting form responses as CSV.
+CSV_DELIMITER = getattr(settings, "FORMS_BUILDER_CSV_DELIMITER", ",")
+
+# The maximum allowed length for field help text
+HELPTEXT_MAX_LENGTH = getattr(settings, "FORMS_BUILDER_HELPTEXT_MAX_LENGTH", 100)
+
+# The maximum allowed length for field choices
+CHOICES_MAX_LENGTH = getattr(settings, "FORMS_BUILDER_CHOICES_MAX_LENGTH", 1000)
+
+# Does sending emails fail silently or raise an exception.
+EMAIL_FAIL_SILENTLY = getattr(settings, "FORMS_BUILDER_EMAIL_FAIL_SILENTLY",
+ settings.DEBUG)
+
+# Django SITE_ID - need a default since no longer provided in settings.py.
+SITE_ID = getattr(settings, "SITE_ID", 1)
--- /dev/null
+from __future__ import unicode_literals
+
+from django.dispatch import Signal
+
+form_invalid = Signal(providing_args=["form"])
+form_valid = Signal(providing_args=["form", "entry"])
--- /dev/null
+# encoding: 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 'Form'
+ db.create_table('forms_form', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('title', self.gf('django.db.models.fields.CharField')(max_length=50)),
+ ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=100, db_index=True)),
+ ('intro', self.gf('django.db.models.fields.TextField')(blank=True)),
+ ('button_text', self.gf('django.db.models.fields.CharField')(default=u'Submit', max_length=50)),
+ ('response', self.gf('django.db.models.fields.TextField')(blank=True)),
+ ('status', self.gf('django.db.models.fields.IntegerField')(default=2)),
+ ('publish_date', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
+ ('expiry_date', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
+ ('login_required', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ('send_email', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('email_from', self.gf('django.db.models.fields.EmailField')(max_length=75, blank=True)),
+ ('email_copies', self.gf('django.db.models.fields.CharField')(max_length=200, blank=True)),
+ ('email_subject', self.gf('django.db.models.fields.CharField')(max_length=200, blank=True)),
+ ('email_message', self.gf('django.db.models.fields.TextField')(blank=True)),
+ ))
+ db.send_create_signal('forms', ['Form'])
+
+ # Adding M2M table for field sites on 'Form'
+ db.create_table('forms_form_sites', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('form', models.ForeignKey(orm['forms.form'], null=False)),
+ ('site', models.ForeignKey(orm['sites.site'], null=False))
+ ))
+ db.create_unique('forms_form_sites', ['form_id', 'site_id'])
+
+ # Adding model 'Field'
+ db.create_table('forms_field', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('label', self.gf('django.db.models.fields.CharField')(max_length=200)),
+ ('field_type', self.gf('django.db.models.fields.IntegerField')()),
+ ('required', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('visible', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ('choices', self.gf('django.db.models.fields.CharField')(max_length=1000, blank=True)),
+ ('default', self.gf('django.db.models.fields.CharField')(max_length=2000, blank=True)),
+ ('placeholder_text', self.gf('django.db.models.fields.CharField')(max_length=100, blank=True)),
+ ('help_text', self.gf('django.db.models.fields.CharField')(max_length=100, blank=True)),
+ ('form', self.gf('django.db.models.fields.related.ForeignKey')(related_name='fields', to=orm['forms.Form'])),
+ ('_order', self.gf('django.db.models.fields.IntegerField')(default=0)),
+ ))
+ db.send_create_signal('forms', ['Field'])
+
+ # Adding model 'FormEntry'
+ db.create_table('forms_formentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('entry_time', self.gf('django.db.models.fields.DateTimeField')()),
+ ('form', self.gf('django.db.models.fields.related.ForeignKey')(related_name='entries', to=orm['forms.Form'])),
+ ))
+ db.send_create_signal('forms', ['FormEntry'])
+
+ # Adding model 'FieldEntry'
+ db.create_table('forms_fieldentry', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('field_id', self.gf('django.db.models.fields.IntegerField')()),
+ ('value', self.gf('django.db.models.fields.CharField')(max_length=2000)),
+ ('entry', self.gf('django.db.models.fields.related.ForeignKey')(related_name='fields', to=orm['forms.FormEntry'])),
+ ))
+ db.send_create_signal('forms', ['FieldEntry'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'Form'
+ db.delete_table('forms_form')
+
+ # Removing M2M table for field sites on 'Form'
+ db.delete_table('forms_form_sites')
+
+ # Deleting model 'Field'
+ db.delete_table('forms_field')
+
+ # Deleting model 'FormEntry'
+ db.delete_table('forms_formentry')
+
+ # Deleting model 'FieldEntry'
+ db.delete_table('forms_fieldentry')
+
+
+ models = {
+ 'forms.field': {
+ 'Meta': {'ordering': "('_order',)", 'object_name': 'Field'},
+ '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'choices': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
+ 'default': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'blank': 'True'}),
+ 'field_type': ('django.db.models.fields.IntegerField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': "orm['forms.Form']"}),
+ 'help_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'placeholder_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'required': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'forms.fieldentry': {
+ 'Meta': {'object_name': 'FieldEntry'},
+ 'entry': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': "orm['forms.FormEntry']"}),
+ 'field_id': ('django.db.models.fields.IntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '2000'})
+ },
+ 'forms.form': {
+ 'Meta': {'object_name': 'Form'},
+ 'button_text': ('django.db.models.fields.CharField', [], {'default': "u'Submit'", 'max_length': '50'}),
+ 'email_copies': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'email_from': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'email_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'email_subject': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'expiry_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'intro': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'response': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'send_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sites.Site']", 'symmetrical': 'False'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'forms.formentry': {
+ 'Meta': {'object_name': 'FormEntry'},
+ 'entry_time': ('django.db.models.fields.DateTimeField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entries'", 'to': "orm['forms.Form']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ 'sites.site': {
+ 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
+ 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ }
+ }
+
+ complete_apps = ['forms']
--- /dev/null
+# encoding: 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 field 'Field.order'
+ db.add_column('forms_field', 'order', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True), keep_default=False)
+
+
+ def backwards(self, orm):
+
+ # Deleting field 'Field.order'
+ db.delete_column('forms_field', 'order')
+
+
+ models = {
+ 'forms.field': {
+ 'Meta': {'ordering': "('_order',)", 'object_name': 'Field'},
+ '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'choices': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
+ 'default': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'blank': 'True'}),
+ 'field_type': ('django.db.models.fields.IntegerField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': "orm['forms.Form']"}),
+ 'help_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'placeholder_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'required': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'forms.fieldentry': {
+ 'Meta': {'object_name': 'FieldEntry'},
+ 'entry': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': "orm['forms.FormEntry']"}),
+ 'field_id': ('django.db.models.fields.IntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '2000'})
+ },
+ 'forms.form': {
+ 'Meta': {'object_name': 'Form'},
+ 'button_text': ('django.db.models.fields.CharField', [], {'default': "u'Submit'", 'max_length': '50'}),
+ 'email_copies': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'email_from': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'email_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'email_subject': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'expiry_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'intro': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'response': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'send_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sites.Site']", 'symmetrical': 'False'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'forms.formentry': {
+ 'Meta': {'object_name': 'FormEntry'},
+ 'entry_time': ('django.db.models.fields.DateTimeField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entries'", 'to': "orm['forms.Form']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ 'sites.site': {
+ 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
+ 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ }
+ }
+
+ complete_apps = ['forms']
--- /dev/null
+# -*- 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 field 'Field.slug'
+ db.add_column('forms_field', 'slug',
+ self.gf('django.db.models.fields.SlugField')(default='', max_length=100, blank=True),
+ keep_default=False)
+ db.create_unique('forms_field', ['slug', 'form_id'])
+
+ def backwards(self, orm):
+ # Deleting field 'Field.slug'
+ db.delete_column('forms_field', 'slug')
+ db.delete_unique('forms_field', ['slug', 'form_id'])
+
+ models = {
+ 'forms.field': {
+ 'Meta': {'ordering': "('_order',)", 'object_name': 'Field'},
+ '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'choices': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
+ 'default': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'blank': 'True'}),
+ 'field_type': ('django.db.models.fields.IntegerField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': "orm['forms.Form']"}),
+ 'help_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'placeholder_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'required': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
+ 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'forms.fieldentry': {
+ 'Meta': {'object_name': 'FieldEntry'},
+ 'entry': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': "orm['forms.FormEntry']"}),
+ 'field_id': ('django.db.models.fields.IntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '2000'})
+ },
+ 'forms.form': {
+ 'Meta': {'object_name': 'Form'},
+ 'button_text': ('django.db.models.fields.CharField', [], {'default': "u'Submit'", 'max_length': '50'}),
+ 'email_copies': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'email_from': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'email_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'email_subject': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'expiry_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'intro': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'response': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'send_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'default': '[1]', 'to': "orm['sites.Site']", 'symmetrical': 'False'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'forms.formentry': {
+ 'Meta': {'object_name': 'FormEntry'},
+ 'entry_time': ('django.db.models.fields.DateTimeField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entries'", 'to': "orm['forms.Form']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ 'sites.site': {
+ 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
+ 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ }
+ }
+
+ complete_apps = ['forms']
--- /dev/null
+# -*- 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):
+ # Changing field 'FieldEntry.value'
+ db.alter_column('forms_fieldentry', 'value', self.gf('django.db.models.fields.CharField')(max_length=2000, null=True))
+
+ def backwards(self, orm):
+ # User chose to not deal with backwards NULL issues for 'FieldEntry.value'
+ raise RuntimeError("Cannot reverse this migration. 'FieldEntry.value' and its values cannot be restored.")
+
+ models = {
+ 'forms.field': {
+ 'Meta': {'ordering': "('_order',)", 'object_name': 'Field'},
+ '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'choices': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
+ 'default': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'blank': 'True'}),
+ 'field_type': ('django.db.models.fields.IntegerField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': "orm['forms.Form']"}),
+ 'help_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'placeholder_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'required': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'forms.fieldentry': {
+ 'Meta': {'object_name': 'FieldEntry'},
+ 'entry': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': "orm['forms.FormEntry']"}),
+ 'field_id': ('django.db.models.fields.IntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'null': 'True'})
+ },
+ 'forms.form': {
+ 'Meta': {'object_name': 'Form'},
+ 'button_text': ('django.db.models.fields.CharField', [], {'default': "u'Submit'", 'max_length': '50'}),
+ 'email_copies': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'email_from': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'email_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'email_subject': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'expiry_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'intro': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'response': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'send_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'default': '[1]', 'to': "orm['sites.Site']", 'symmetrical': 'False'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'forms.formentry': {
+ 'Meta': {'object_name': 'FormEntry'},
+ 'entry_time': ('django.db.models.fields.DateTimeField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entries'", 'to': "orm['forms.Form']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ 'sites.site': {
+ 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
+ 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ }
+ }
+
+ complete_apps = ['forms']
--- /dev/null
+# -*- 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):
+ for field in orm.Field.objects.filter(slug=''):
+ field.slug = "field_%s" % field.id
+ field.save()
+
+ def backwards(self, orm):
+ "Write your backwards methods here."
+
+
+ models = {
+ 'forms.field': {
+ 'Meta': {'ordering': "('_order',)", 'unique_together': "(('form', 'slug'),)", 'object_name': 'Field'},
+ '_order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'choices': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
+ 'default': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'blank': 'True'}),
+ 'field_type': ('django.db.models.fields.IntegerField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': "orm['forms.Form']"}),
+ 'help_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'placeholder_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'required': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
+ 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'forms.fieldentry': {
+ 'Meta': {'object_name': 'FieldEntry'},
+ 'entry': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': "orm['forms.FormEntry']"}),
+ 'field_id': ('django.db.models.fields.IntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '2000'})
+ },
+ 'forms.form': {
+ 'Meta': {'object_name': 'Form'},
+ 'button_text': ('django.db.models.fields.CharField', [], {'default': "u'Submit'", 'max_length': '50'}),
+ 'email_copies': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'email_from': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'email_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'email_subject': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'expiry_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'intro': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'response': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'send_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'default': '[1]', 'to': "orm['sites.Site']", 'symmetrical': 'False'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'forms.formentry': {
+ 'Meta': {'object_name': 'FormEntry'},
+ 'entry_time': ('django.db.models.fields.DateTimeField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entries'", 'to': "orm['forms.Form']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ 'sites.site': {
+ 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
+ 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ }
+ }
+
+ complete_apps = ['forms']
+ symmetrical = True
--- /dev/null
+# -*- 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):
+
+ # Changing field 'FieldEntry.value'
+ db.alter_column('forms_fieldentry', 'value', self.gf('django.db.models.fields.CharField')(max_length=2000, null=True))
+ # Deleting field 'Field._order'
+ db.delete_column('forms_field', '_order')
+
+
+ # Changing field 'Field.placeholder_text'
+ db.alter_column('forms_field', 'placeholder_text', self.gf('django.db.models.fields.CharField')(max_length=100, null=True))
+ def backwards(self, orm):
+
+ # Changing field 'FieldEntry.value'
+ db.alter_column('forms_fieldentry', 'value', self.gf('django.db.models.fields.CharField')(default=None, max_length=2000))
+ # Adding field 'Field._order'
+ db.add_column('forms_field', '_order',
+ self.gf('django.db.models.fields.IntegerField')(default=0),
+ keep_default=False)
+
+
+ # Changing field 'Field.placeholder_text'
+ db.alter_column('forms_field', 'placeholder_text', self.gf('django.db.models.fields.CharField')(default='', max_length=100))
+ models = {
+ 'forms.field': {
+ 'Meta': {'ordering': "('order',)", 'unique_together': "(('form', 'slug'),)", 'object_name': 'Field'},
+ 'choices': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
+ 'default': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'blank': 'True'}),
+ 'field_type': ('django.db.models.fields.IntegerField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': "orm['forms.Form']"}),
+ 'help_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'placeholder_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'required': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
+ 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'forms.fieldentry': {
+ 'Meta': {'object_name': 'FieldEntry'},
+ 'entry': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': "orm['forms.FormEntry']"}),
+ 'field_id': ('django.db.models.fields.IntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'null': 'True'})
+ },
+ 'forms.form': {
+ 'Meta': {'object_name': 'Form'},
+ 'button_text': ('django.db.models.fields.CharField', [], {'default': "u'Submit'", 'max_length': '50'}),
+ 'email_copies': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'email_from': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'email_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'email_subject': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'expiry_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'intro': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'response': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'send_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'default': '[1]', 'to': "orm['sites.Site']", 'symmetrical': 'False'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'forms.formentry': {
+ 'Meta': {'object_name': 'FormEntry'},
+ 'entry_time': ('django.db.models.fields.DateTimeField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entries'", 'to': "orm['forms.Form']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ 'sites.site': {
+ 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
+ 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ }
+ }
+
+ complete_apps = ['forms']
\ No newline at end of file
--- /dev/null
+# -*- 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 'Field', fields ['slug', 'form']
+ db.delete_unique(u'forms_field', ['slug', 'form_id'])
+
+
+ def backwards(self, orm):
+ # Adding unique constraint on 'Field', fields ['slug', 'form']
+ db.create_unique(u'forms_field', ['slug', 'form_id'])
+
+
+ models = {
+ u'forms.field': {
+ 'Meta': {'ordering': "('order',)", 'object_name': 'Field'},
+ 'choices': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
+ 'default': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'blank': 'True'}),
+ 'field_type': ('django.db.models.fields.IntegerField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': u"orm['forms.Form']"}),
+ 'help_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'placeholder_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'required': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
+ 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'forms.fieldentry': {
+ 'Meta': {'object_name': 'FieldEntry'},
+ 'entry': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': u"orm['forms.FormEntry']"}),
+ 'field_id': ('django.db.models.fields.IntegerField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'null': 'True'})
+ },
+ u'forms.form': {
+ 'Meta': {'object_name': 'Form'},
+ 'button_text': ('django.db.models.fields.CharField', [], {'default': "u'Submit'", 'max_length': '50'}),
+ 'email_copies': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'email_from': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'email_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'email_subject': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'expiry_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'intro': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'response': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'send_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'default': '[1]', 'to': u"orm['sites.Site']", 'symmetrical': 'False'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ u'forms.formentry': {
+ 'Meta': {'object_name': 'FormEntry'},
+ 'entry_time': ('django.db.models.fields.DateTimeField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entries'", 'to': u"orm['forms.Form']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ u'sites.site': {
+ 'Meta': {'ordering': "(u'domain',)", 'object_name': 'Site', 'db_table': "u'django_site'"},
+ 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ }
+ }
+
+ complete_apps = ['forms']
\ No newline at end of file
--- /dev/null
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'Form.redirect_url'
+ db.add_column('forms_form', 'redirect_url',
+ self.gf('django.db.models.fields.CharField')(null=True, max_length=200, blank=True),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Deleting field 'Form.redirect_url'
+ db.delete_column('forms_form', 'redirect_url')
+
+
+ models = {
+ 'forms.field': {
+ 'Meta': {'object_name': 'Field', 'ordering': "('order',)"},
+ 'choices': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
+ 'default': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'blank': 'True'}),
+ 'field_type': ('django.db.models.fields.IntegerField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': "orm['forms.Form']"}),
+ 'help_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'placeholder_text': ('django.db.models.fields.CharField', [], {'null': 'True', 'max_length': '100', 'blank': 'True'}),
+ 'required': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'default': "''", 'blank': 'True'}),
+ 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'forms.fieldentry': {
+ 'Meta': {'object_name': 'FieldEntry'},
+ 'entry': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': "orm['forms.FormEntry']"}),
+ 'field_id': ('django.db.models.fields.IntegerField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'value': ('django.db.models.fields.CharField', [], {'null': 'True', 'max_length': '2000'})
+ },
+ 'forms.form': {
+ 'Meta': {'object_name': 'Form'},
+ 'button_text': ('django.db.models.fields.CharField', [], {'max_length': '50', 'default': "'Submit'"}),
+ 'email_copies': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'email_from': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'email_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'email_subject': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+ 'expiry_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'intro': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'publish_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'redirect_url': ('django.db.models.fields.CharField', [], {'null': 'True', 'max_length': '200', 'blank': 'True'}),
+ 'response': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'send_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'forms_form_forms'", 'symmetrical': 'False', 'default': '[1]', 'to': "orm['sites.Site']"}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'forms.formentry': {
+ 'Meta': {'object_name': 'FormEntry'},
+ 'entry_time': ('django.db.models.fields.DateTimeField', [], {}),
+ 'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entries'", 'to': "orm['forms.Form']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ 'sites.site': {
+ 'Meta': {'db_table': "'django_site'", 'object_name': 'Site', 'ordering': "('domain',)"},
+ 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ }
+ }
+
+ complete_apps = ['forms']
\ No newline at end of file
--- /dev/null
+{% extends "admin/change_form.html" %}
+
+{% load i18n %}
+
+{% block object-tools %}
+{% if change %}{% if not is_popup %}
+<ul class="object-tools">
+ <li>
+ <a href="{% url "admin:form_entries" object_id %}">{% trans "View entries" %}</a>
+ </li>
+ <li>
+ <a href="history/" class="historylink">{% trans "History" %}</a>
+ </li>
+ {% if has_absolute_url %}
+ <li>
+ <a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a>
+ </li>
+ {% endif%}
+</ul>
+<style>
+#fields-group .vTextField {width: 13.5em;}
+#fields-group .vIntegerField {width: 2em;}
+#fields-group .field-required,
+#fields-group .field-visible,
+#fields-group .delete {text-align: center;}
+form .aligned p.help {padding-left: 24px;}
+</style>
+{% endif %}{% endif %}
+{% endblock %}
--- /dev/null
+{% extends "admin/base_site.html" %}
+
+{% load i18n %}
+
+{% block extrahead %}
+{{ block.super }}
+<style>
+#content-main table {margin:10px 0 20px 0;}
+#content-main ul, #content-main li {display:inline; padding:0;}
+#content-main label {cursor:pointer;}
+h1 {margin-top:20px;}
+#content-main h1 {margin:30px 0 0 0;}
+table {border:1px solid #ddd; width:100%;}
+th, td {border-right:1px solid #ddd; padding:5px 15px;}
+.last {border-right:0;}
+td.include, th.include-all, td.include-all {text-align:center; width:10px;}
+.empty {margin-top:10px;}
+.field {padding-top:8px;}
+.filter select {width:140px;}
+.options {vertical-align:middle; white-space:nowrap;}
+.options label {margin:0 10px; font-weight:normal; vertical-align:middle;}
+.options-div {visibility:hidden;}
+.options select {min-width:0;}
+.on {background:#eee;}
+.off {background:#fff;}
+.button {float:left !important; margin-right:10px; cursor:pointer;}
+</style>
+<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
+<script>
+$(function() {
+ // Show the filter criteria fields when a filter option is selected.
+ $('.filter select').change(function() {
+ var filtering = this.selectedIndex > 0;
+ var options = $(this).parent().parent().find('.options-div');
+ options.css('visibility', filtering ? 'visible' : 'hidden');
+ // Focus the first field.
+ if (filtering) {
+ var input = options.find('input:first');
+ if (input.length === 1) {
+ input.focus();
+ } else {
+ options.find('select:first').focus();
+ }
+ }
+ }).change();
+ // Toggle the include `All` checkboxes - grouped within table tags.
+ $('#content-main table').each(function(i, table) {
+ table = $(table);
+ var all = table.find('.include-all :checkbox');
+ var others = table.find('.include :checkbox');
+ others.change(function() {
+ var len = table.find('.include :checkbox:not(:checked)').length;
+ all.attr('checked', len === 0);
+ });
+ all.change(function() {
+ others.attr('checked', !!all.attr('checked'));
+ });
+ });
+ // Add a confirmation prompt for deleting entries.
+ $('input[name="delete"]').click(function() {
+ if ( $('input[name="selected"]:checked').length === 0 ) {
+ alert('{% trans "No entries selected" %}');
+ return false;
+ } else {
+ return confirm('{% trans "Delete selected entries?" %}');
+ }
+ });
+});
+</script>
+{% endblock %}
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+ <a href="../../../../">{% trans "Home" %}</a> ›
+ <a href="../../../">{{ opts.app_label|capfirst|escape }}</a> ›
+ <a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> ›
+ <a href="../">{{ original|truncatewords:"18" }}</a> ›
+ {{ title }}
+</div>
+{% endblock %}
+
+{% block content %}
+<div id="content-main">
+ <form method="post">
+ {% csrf_token %}
+ <table>
+ <tr>
+ <th>{% trans "Field" %}</th>
+ <th>{% trans "Include" %}</th>
+ <th class="last" colspan="2">{% trans "Filter by" %}</th>
+ </tr>
+ {% for include_field, filter_field, filter_option_fields in entries_form %}
+ <tr class="{% cycle on,off as row %}">
+ <td class="field">{{ include_field.label_tag }}</td>
+ <td class="include">{{ include_field }}</td>
+ <td class="filter last">{{ filter_field }}</td>
+ <td class="options last">
+ <div class="options-div">
+ {% for option_field in filter_option_fields %}
+ {{ option_field.label_tag }} {{ option_field }}
+ {% endfor %}
+ </div>
+ </td>
+ </tr>
+ {% endfor %}
+ <tr class="{% cycle row %}">
+ <td class="field" style="text-align:right;">
+ <label for="include-all">{% trans "All" %}</label>
+ </td>
+ <td class="include-all">
+ <input type="checkbox" id="include-all" class="include-all" checked="checked">
+ </td>
+ <td class="last" colspan="2"> </td>
+ </tr>
+ </table>
+ <input type="submit" name="back" class="button" value="{% trans "Back to form" %}">
+ <input type="submit" class="button default" value="{% trans "View entries" %}">
+ <input type="submit" class="button default" name="export" value="{% trans "Export CSV" %}">
+ {% if xlwt_installed %}
+ <input type="submit" class="button default" name="export_xls" value="{% trans "Export XLS" %}">
+ {% endif %}
+ {% if submitted %}
+ <br clear="both" />
+ <h1 id="entries-title">{% trans "Entries" %}</h1>
+ {% for row in entries_form.rows %}
+ {% if forloop.first %}
+ <table id="entries-table">
+ <tr>
+ {% if can_delete_entries %}
+ <th class="include-all"><input type="checkbox"></th>
+ {% endif %}
+ {% for column in entries_form.columns %}
+ <th{% if forloop.last %} class="last"{% endif %}>{{ column }}</th>
+ {% endfor %}
+ </tr>
+ {% endif %}
+ <tr class="{% cycle on,off %}">
+ {% if can_delete_entries %}
+ <td class="include">
+ <input type="checkbox" name="selected" value="{{ row.0 }}">
+ </td>
+ {% endif %}
+ {% for field in row %}
+ {% if not forloop.first %}
+ <td{% if forloop.last %} class="last"{% endif %}>{{ field }}</td>
+ {% endif %}
+ {% endfor %}
+ </tr>
+ {% if forloop.last %}
+ </table>
+ <!--
+ count is injected into the title here with the :after selector
+ to avoid calling the expensive "entries_form.rows" twice.
+ -->
+ <style>#entries-title:after {content: ' ({{ forloop.counter }})';}</style>
+ {% if can_delete_entries %}
+ <input type="submit" name="back" class="button" value="{% trans "Back to form" %}">
+ <input type="submit" name="delete" class="button default" value="{% trans "Delete selected" %}">
+ {% endif %}
+ {% endif %}
+ {% empty %}
+ <p class="empty">{% trans "No entries to display" %}</p>
+ {% endfor %}
+ {% endif %}
+ </form>
+</div>
+{% endblock %}
--- /dev/null
+{% block main %}{% endblock %}
+
+<br><a href="http://{{ request.get_host }}">http://{{ request.get_host }}</a>
--- /dev/null
+{% block main %}{% endblock %}
+
+http://{{ request.get_host }}
--- /dev/null
+{% extends "email_extras/base.html" %}
+
+{% block main %}
+{% if message %}<p>{{ message }}</p>{% endif %}
+<table border="0">
+{% for field, value in fields %}
+<tr>
+ <td><b>{{ field }}:</b></td>
+ <td>{{ value|linebreaks }}</td>
+</tr>
+{% endfor %}
+</table>
+{% endblock %}
--- /dev/null
+{% extends "email_extras/base.txt" %}
+
+{% block main %}{% if message %}
+{{ message }}
+
+{% endif %}{% for field, value in fields %}
+{{ field }}: {{ value|safe }}
+{% endfor %}
+{% endblock %}
--- /dev/null
+{% extends "email_extras/form_response.html" %}
+
+{% block main %}
+{{ block.super }}
+{% endblock %}
--- /dev/null
+{% extends "email_extras/form_response.txt" %}
+
+{% block main %}
+{{ block.super }}
+{% endblock %}
--- /dev/null
+<!doctype html>
+{% load forms_builder_tags %}
+<head>
+ <meta charset="utf-8">
+ <title>{{ form.title }}</title>
+ <style>
+ body {font-family:sans-serif; padding:1em 2em;}
+ p {width:50em; clear:both;}
+ label {display:block; float:left; width:8em; margin:0 1.2em 1.2em 0;}
+ li {list-style-type:none;}
+ li label {width:auto; cursor:pointer;}
+ .errorlist {color:#f00;}
+ </style>
+</head>
+<body>
+ {% render_built_form form %}
+</body>
--- /dev/null
+<!doctype html>
+<head>
+ <meta charset="utf-8">
+ <title>{{ form.title }}</title>
+ <style>
+ body {font-family:sans-serif; padding:1em 2em;}
+ p {width:50em;}
+ label {display:block; float:left; width:10em;}
+ .errorlist {color:#f00;}
+ </style>
+</head>
+<body>
+ <h1>{{ form.title }}</h1>
+ {% if form.response %}
+ <p>{{ form.response }}</p>
+ {% endif %}
+</body>
--- /dev/null
+ <h1>{{ form.title }}</h1>
+ {% if form.intro %}
+ <p>{{ form.intro }}</p>
+ {% endif %}
+ {{ form_for_form.media }}
+ <form action="{{ form.get_absolute_url }}" method="post"
+ {% if form_for_form.is_multipart %}enctype="multipart/form-data"{% endif %}>
+ {% csrf_token %}
+ {{ form_for_form.as_p }}
+ <div style="clear:left;"> </div>
+ <input type="submit" value="{{ form.button_text }}">
+ </form>
--- /dev/null
+from __future__ import unicode_literals
+from django import template
+from forms_builder.forms.models import Form
+from forms_builder.forms.forms import EntriesForm
+
+register = template.Library()
+
+@register.assignment_tag(takes_context=True)
+def form_count(context, form=None, id=None, slug=None, **kwargs):
+ if id is not None:
+ form = Form.objects.get(pk=id)
+ elif slug is not None:
+ form = Form.objects.get(slug=slug)
+ ef = EntriesForm(form, context.get('request', None), data=kwargs)
+ return sum(1 for i in ef.rows())
+
+
+@register.assignment_tag(name='set')
+def set_tag(sth):
+ return sth
--- /dev/null
+from __future__ import unicode_literals
+from future.builtins import str
+
+from django import template
+from django.template.loader import get_template
+
+from forms_builder.forms.forms import FormForForm
+from forms_builder.forms.models import Form
+
+
+register = template.Library()
+
+
+class BuiltFormNode(template.Node):
+
+ def __init__(self, name, value):
+ self.name = name
+ self.value = value
+
+ def render(self, context):
+ request = context["request"]
+ user = getattr(request, "user", None)
+ post = getattr(request, "POST", None)
+ files = getattr(request, "FILES", None)
+ if self.name != "form":
+ lookup_value = template.Variable(self.value).resolve(context)
+ try:
+ form = Form.objects.get(**{str(self.name): lookup_value})
+ except Form.DoesNotExist:
+ form = None
+ else:
+ form = template.Variable(self.value).resolve(context)
+ if not isinstance(form, Form) or not form.published(for_user=user):
+ return ""
+ t = get_template("forms/includes/built_form.html")
+ context["form"] = form
+ form_args = (form, context, post or None, files or None)
+ context["form_for_form"] = FormForForm(*form_args)
+ return t.render(context)
+
+
+@register.tag
+def render_built_form(parser, token):
+ """
+ render_build_form takes one argument in one of the following formats:
+
+ {% render_build_form form_instance %}
+ {% render_build_form form=form_instance %}
+ {% render_build_form id=form_instance.id %}
+ {% render_build_form slug=form_instance.slug %}
+
+ """
+ try:
+ _, arg = token.split_contents()
+ if "=" not in arg:
+ arg = "form=" + arg
+ name, value = arg.split("=", 1)
+ if name not in ("form", "id", "slug"):
+ raise ValueError
+ except ValueError:
+ e = ()
+ raise template.TemplateSyntaxError(render_built_form.__doc__)
+ return BuiltFormNode(name, value)
--- /dev/null
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.contrib.auth.models import User, AnonymousUser
+from django.contrib.sites.models import Site
+from django.db import IntegrityError
+from django.http import HttpResponseRedirect
+from django.template import Context, RequestContext, Template
+from django.test import TestCase
+
+from forms_builder.forms.fields import NAMES, FILE
+from forms_builder.forms.forms import FormForForm
+from forms_builder.forms.models import (Form, Field,
+ STATUS_DRAFT, STATUS_PUBLISHED)
+from forms_builder.forms.settings import USE_SITES
+from forms_builder.forms.signals import form_invalid, form_valid
+
+
+class Tests(TestCase):
+
+ def setUp(self):
+ self._site = Site.objects.get_current()
+
+ def test_form_fields(self):
+ """
+ Simple 200 status check against rendering and posting to forms with
+ both optional and required fields.
+ """
+ for required in (True, False):
+ form = Form.objects.create(title="Test", status=STATUS_PUBLISHED)
+ if USE_SITES:
+ form.sites.add(self._site)
+ form.save()
+ for (field, _) in NAMES:
+ form.fields.create(label=field, field_type=field,
+ required=required, visible=True)
+ response = self.client.get(form.get_absolute_url())
+ self.assertEqual(response.status_code, 200)
+ fields = form.fields.visible()
+ data = dict([(f.slug, "test") for f in fields])
+ response = self.client.post(form.get_absolute_url(), data=data)
+ self.assertEqual(response.status_code, 200)
+
+ def test_draft_form(self):
+ """
+ Test that a form with draft status is only visible to staff.
+ """
+ settings.DEBUG = True # Don't depend on having a 404 template.
+ username = "test"
+ password = "test"
+ User.objects.create_superuser(username, "", password)
+ self.client.logout()
+ draft = Form.objects.create(title="Draft", status=STATUS_DRAFT)
+ if USE_SITES:
+ draft.sites.add(self._site)
+ draft.save()
+ response = self.client.get(draft.get_absolute_url())
+ self.assertEqual(response.status_code, 404)
+ self.client.login(username=username, password=password)
+ response = self.client.get(draft.get_absolute_url())
+ self.assertEqual(response.status_code, 200)
+
+ def test_form_signals(self):
+ """
+ Test that each of the signals are sent.
+ """
+ events = ["valid", "invalid"]
+ invalid = lambda **kwargs: events.remove("invalid")
+ form_invalid.connect(invalid)
+ valid = lambda **kwargs: events.remove("valid")
+ form_valid.connect(valid)
+ form = Form.objects.create(title="Signals", status=STATUS_PUBLISHED)
+ if USE_SITES:
+ form.sites.add(self._site)
+ form.save()
+ form.fields.create(label="field", field_type=NAMES[0][0],
+ required=True, visible=True)
+ self.client.post(form.get_absolute_url(), data={})
+ data = {form.fields.visible()[0].slug: "test"}
+ self.client.post(form.get_absolute_url(), data=data)
+ self.assertEqual(len(events), 0)
+
+ def test_tag(self):
+ """
+ Test that the different formats for the ``render_built_form``
+ tag all work.
+ """
+ form = Form.objects.create(title="Tags", status=STATUS_PUBLISHED)
+ request = type(str(""), (), {"META": {}, "user": AnonymousUser()})()
+ context = RequestContext(request, {"form": form})
+ template = "{%% load forms_builder_tags %%}{%% render_built_form %s %%}"
+ formats = ("form", "form=form", "id=form.id", "slug=form.slug")
+ for format in formats:
+ t = Template(template % format).render(context)
+ self.assertTrue(form.get_absolute_url(), t)
+
+ def test_optional_filefield(self):
+ form = Form.objects.create(title="Test", status=STATUS_PUBLISHED)
+ if USE_SITES:
+ form.sites.add(self._site)
+ form.save()
+ form.fields.create(label="file field",
+ field_type=FILE,
+ required=False,
+ visible=True)
+ fields = form.fields.visible()
+ data = {'field_%s' % fields[0].id: ''}
+ context = Context({})
+ form_for_form = FormForForm(form, context, data=data)
+ # Should not raise IntegrityError: forms_fieldentry.value
+ # may not be NULL
+ form_for_form.save()
+
+
+ def test_field_validate_slug_names(self):
+ form = Form.objects.create(title="Test")
+ field = Field(form=form,
+ label="First name", field_type=NAMES[0][0])
+ field.save()
+ self.assertEqual(field.slug, 'first_name')
+
+ field_2 = Field(form=form,
+ label="First name", field_type=NAMES[0][0])
+ try:
+ field_2.save()
+ except IntegrityError:
+ self.fail("Slugs were not auto-unique")
+
+ def test_field_default_ordering(self):
+ form = Form.objects.create(title="Test")
+ form.fields.create(label="second field",
+ field_type=NAMES[0][0], order=2)
+ f1 = form.fields.create(label="first field",
+ field_type=NAMES[0][0], order=1)
+ self.assertEqual(form.fields.all()[0], f1)
+
+ def test_form_errors(self):
+ from future.builtins import str
+ form = Form.objects.create(title="Test")
+ if USE_SITES:
+ form.sites.add(self._site)
+ form.save()
+ form.fields.create(label="field", field_type=NAMES[0][0],
+ required=True, visible=True)
+ response = self.client.post(form.get_absolute_url(), {"foo": "bar"})
+ self.assertTrue("This field is required" in str(response.content))
+
+ def test_form_redirect(self):
+ redirect_url = 'http://example.com/foo'
+ form = Form.objects.create(title='Test', redirect_url=redirect_url)
+ if USE_SITES:
+ form.sites.add(self._site)
+ form.save()
+ form.fields.create(label='field', field_type=NAMES[3][0],
+ required=True, visible=True)
+ form_absolute_url = form.get_absolute_url()
+ response = self.client.post(form_absolute_url, {'field': '0'})
+ self.assertEqual(response["location"], redirect_url)
+ response = self.client.post(form_absolute_url, {'field': 'bar'})
+ self.assertFalse(isinstance(response, HttpResponseRedirect))
--- /dev/null
+from __future__ import unicode_literals
+
+from django.conf.urls import patterns, url
+
+
+urlpatterns = patterns("forms_builder.forms.views",
+ url(r"(?P<slug>.*)/sent/$", "form_sent", name="form_sent"),
+ url(r"(?P<slug>.*)/$", "form_detail", name="form_detail"),
+)
--- /dev/null
+from __future__ import unicode_literals
+
+from django.template.defaultfilters import slugify as django_slugify
+from importlib import import_module
+from unidecode import unidecode
+
+
+# Timezone support with fallback.
+try:
+ from django.utils.timezone import now
+except ImportError:
+ from datetime import datetime
+ now = datetime.now
+
+
+def slugify(s):
+ """
+ Translates unicode into closest possible ascii chars before
+ slugifying.
+ """
+ from future.builtins import str
+ return django_slugify(unidecode(str(s)))
+
+
+def unique_slug(manager, slug_field, slug):
+ """
+ Ensure slug is unique for the given manager, appending a digit
+ if it isn't.
+ """
+ i = 0
+ while True:
+ if i > 0:
+ if i > 1:
+ slug = slug.rsplit("-", 1)[0]
+ slug = "%s-%s" % (slug, i)
+ if not manager.filter(**{slug_field: slug}):
+ break
+ i += 1
+ return slug
+
+
+def split_choices(choices_string):
+ """
+ Convert a comma separated choices string to a list.
+ """
+ return [x.strip() for x in choices_string.split(",") if x.strip()]
+
+
+def html5_field(name, base):
+ """
+ Takes a Django form field class and returns a subclass of
+ it with the given name as its input type.
+ """
+ return type(str(""), (base,), {"input_type": name})
+
+
+def import_attr(path):
+ """
+ Given a a Python dotted path to a variable in a module,
+ imports the module and returns the variable in it.
+ """
+ module_path, attr_name = path.rsplit(".", 1)
+ return getattr(import_module(module_path), attr_name)
--- /dev/null
+from __future__ import unicode_literals
+
+import json
+
+from django.conf import settings
+from django.contrib.auth import REDIRECT_FIELD_NAME
+from django.core.urlresolvers import reverse
+from django.http import HttpResponse
+from django.shortcuts import get_object_or_404, redirect, render_to_response
+from django.template import RequestContext
+from django.utils.http import urlquote
+from django.views.generic.base import TemplateView
+from email_extras.utils import send_mail_template
+
+from forms_builder.forms.forms import FormForForm
+from forms_builder.forms.models import Form
+from forms_builder.forms.settings import EMAIL_FAIL_SILENTLY
+from forms_builder.forms.signals import form_invalid, form_valid
+from forms_builder.forms.utils import split_choices
+
+
+class FormDetail(TemplateView):
+
+ template_name = "forms/form_detail.html"
+
+ def get_context_data(self, **kwargs):
+ context = super(FormDetail, self).get_context_data(**kwargs)
+ published = Form.objects.published(for_user=self.request.user)
+ context["form"] = get_object_or_404(published, slug=kwargs["slug"])
+ return context
+
+ def get(self, request, *args, **kwargs):
+ context = self.get_context_data(**kwargs)
+ login_required = context["form"].login_required
+ if login_required and not request.user.is_authenticated():
+ path = urlquote(request.get_full_path())
+ bits = (settings.LOGIN_URL, REDIRECT_FIELD_NAME, path)
+ return redirect("%s?%s=%s" % bits)
+ return self.render_to_response(context)
+
+ def post(self, request, *args, **kwargs):
+ published = Form.objects.published(for_user=request.user)
+ form = get_object_or_404(published, slug=kwargs["slug"])
+ form_for_form = FormForForm(form, RequestContext(request),
+ request.POST or None,
+ request.FILES or None)
+ if not form_for_form.is_valid():
+ form_invalid.send(sender=request, form=form_for_form)
+ else:
+ # Attachments read must occur before model save,
+ # or seek() will fail on large uploads.
+ attachments = []
+ for f in form_for_form.files.values():
+ f.seek(0)
+ attachments.append((f.name, f.read()))
+ entry = form_for_form.save()
+ form_valid.send(sender=request, form=form_for_form, entry=entry)
+ self.send_emails(request, form_for_form, form, entry, attachments)
+ if not self.request.is_ajax():
+ return redirect(form.redirect_url or
+ reverse("form_sent", kwargs={"slug": form.slug}))
+ context = {"form": form, "form_for_form": form_for_form}
+ return self.render_to_response(context)
+
+ def render_to_response(self, context, **kwargs):
+ if self.request.is_ajax():
+ json_context = json.dumps({
+ "errors": context["form_for_form"].errors,
+ "form": context["form_for_form"].as_p(),
+ "message": context["form"].response,
+ })
+ return HttpResponse(json_context, content_type="application/json")
+ return super(FormDetail, self).render_to_response(context, **kwargs)
+
+ def send_emails(self, request, form_for_form, form, entry, attachments):
+ subject = form.email_subject
+ if not subject:
+ subject = "%s - %s" % (form.title, entry.entry_time)
+ fields = []
+ for (k, v) in form_for_form.fields.items():
+ value = form_for_form.cleaned_data[k]
+ if isinstance(value, list):
+ value = ", ".join([i.strip() for i in value])
+ fields.append((v.label, value))
+ context = {
+ "fields": fields,
+ "message": form.email_message,
+ "request": request,
+ }
+ email_from = form.email_from or settings.DEFAULT_FROM_EMAIL
+ email_to = form_for_form.email_to()
+ if email_to and form.send_email:
+ send_mail_template(subject, "form_response", email_from,
+ [email_to], context=context,
+ fail_silently=EMAIL_FAIL_SILENTLY)
+ headers = None
+ if email_to:
+ headers = {"Reply-To": email_to}
+ email_copies = split_choices(form.email_copies)
+ if email_copies:
+ send_mail_template(subject, "form_response_copies", email_from,
+ email_copies, context=context,
+ attachments=attachments,
+ fail_silently=EMAIL_FAIL_SILENTLY,
+ headers=headers)
+
+form_detail = FormDetail.as_view()
+
+
+def form_sent(request, slug, template="forms/form_sent.html"):
+ """
+ Show the response message.
+ """
+ published = Form.objects.published(for_user=request.user)
+ context = {"form": get_object_or_404(published, slug=slug)}
+ return render_to_response(template, context, RequestContext(request))
--- /dev/null
+from django.contrib import admin
+
+from organizations import models
+
+admin.site.register(models.Organization)
+
--- /dev/null
+from django import forms
+from .models import Organization, UserCard, countries
+
+class OrganizationForm(forms.ModelForm):
+ cts = countries
+
+ class Meta:
+ model = Organization
+ exclude = ['_html']
+
+class UserCardForm(forms.ModelForm):
+ cts = countries
+
+ first_name = forms.CharField(required=False)
+ last_name = forms.CharField(required=False)
+
+ class Meta:
+ model = UserCard
+ exclude = ['_html', 'user']
+
+ def __init__(self, *args, **kwargs):
+ if 'instance' in kwargs:
+ kwargs['initial'] = {
+ 'first_name': kwargs['instance'].user.first_name,
+ 'last_name': kwargs['instance'].user.last_name,
+ }
+ return super(UserCardForm, self).__init__(*args, **kwargs)
+
+ def save(self, *args, **kwargs):
+ self.instance.user.first_name = self.cleaned_data.get('first_name', '')
+ self.instance.user.last_name = self.cleaned_data.get('last_name', '')
+ self.instance.user.save()
+ return super(UserCardForm, self).save(*args, **kwargs)
+
--- /dev/null
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-01-26 11:37+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
+"|| n%100>=20) ? 1 : 2);\n"
+
+#: templates/organizations/edit.html:6
+msgid "Edit organization"
+msgstr "Edytuj organizację"
+
+#: templates/organizations/edit.html:12 templates/organizations/main.html:28
+#: templates/organizations/new.html:12
+msgid "Name"
+msgstr "Nazwa"
+
+#: templates/organizations/edit.html:15 templates/organizations/new.html:15
+msgid "Logo"
+msgstr ""
+
+#: templates/organizations/edit.html:20
+#: templates/organizations/edit_user.html:24
+#: templates/organizations/new.html:20
+#: templates/organizations/snippets/organization.html:12
+#: templates/organizations/snippets/user.html:12
+msgid "Country"
+msgstr "Kraj"
+
+#: templates/organizations/edit.html:27
+#: templates/organizations/edit_user.html:32
+#: templates/organizations/new.html:27
+#: templates/organizations/snippets/organization.html:13
+#: templates/organizations/snippets/user.html:13
+msgid "WWW"
+msgstr ""
+
+#: templates/organizations/edit.html:30
+#: templates/organizations/edit_user.html:35
+#: templates/organizations/new.html:30
+#: templates/organizations/snippets/organization.html:16
+#: templates/organizations/snippets/user.html:16
+msgid "Description"
+msgstr "Opis"
+
+#: templates/organizations/edit.html:33
+#: templates/organizations/edit_user.html:38
+#: templates/organizations/new.html:33
+msgid "Projects"
+msgstr "Projekty"
+
+#: templates/organizations/edit.html:36
+#: templates/organizations/edit_user.html:41
+#: templates/organizations/new.html:36
+msgid "Enter each line as: URL language description"
+msgstr "Wpisz każdą linię w postaci: URL język opis"
+
+#: templates/organizations/edit.html:38
+#: templates/organizations/edit_user.html:43
+msgid "Change"
+msgstr "Zmień"
+
+#: templates/organizations/edit_user.html:6
+msgid "Edit user data"
+msgstr "Edytuj dane użytkownika"
+
+#: templates/organizations/edit_user.html:12
+msgid "First name"
+msgstr "Imię"
+
+#: templates/organizations/edit_user.html:15
+msgid "Last name"
+msgstr "Nazwisko"
+
+#: templates/organizations/edit_user.html:19
+msgid "Photo"
+msgstr "Zdjęcie"
+
+#: templates/organizations/join.html:6
+msgid "Join organization"
+msgstr "Dołącz do organizacji"
+
+#: templates/organizations/join.html:8
+#, python-format
+msgid ""
+"\n"
+" <p>Your membership in <strong><a href=\"%(url)s\">%(org)s</a></strong> "
+"will have to be confirmed by\n"
+" the organization owner.</p>\n"
+" "
+msgstr ""
+"\n"
+"<p>Twoje członkostwo w organizacji <strong><a href=\"%(url)s\">%(org)s</a></strong> musi zostać potwierdzone przez jej właściciela.</p>\n"
+" "
+
+#: templates/organizations/join.html:15 templates/organizations/main.html:10
+msgid "Join"
+msgstr "Dołącz"
+
+#: templates/organizations/main.html:8
+#: templates/organizations/user_card.html:8
+msgid "Edit"
+msgstr "Edytuj"
+
+#: templates/organizations/main.html:13
+msgid "New resource +"
+msgstr "Nowy zasób +"
+
+#: templates/organizations/main.html:21 templates/organizations/main.html:96
+#: templates/organizations/user_card.html:13
+msgid "Resources"
+msgstr "Zasoby"
+
+#: templates/organizations/main.html:22 templates/organizations/main.html:97
+msgid "Members"
+msgstr "Członkowie"
+
+#: templates/organizations/main.html:29
+msgid "E-mail"
+msgstr ""
+
+#: templates/organizations/main.html:30
+msgid "Status"
+msgstr ""
+
+#: templates/organizations/main.html:43
+msgid "owner"
+msgstr "właściciel"
+
+#: templates/organizations/main.html:51
+msgid "Make regular"
+msgstr "Ustaw jako zwykły"
+
+#: templates/organizations/main.html:52 templates/organizations/main.html:81
+msgid "Remove"
+msgstr "Usuń"
+
+#: templates/organizations/main.html:58
+msgid "pending"
+msgstr "czeka"
+
+#: templates/organizations/main.html:65
+msgid "Accept"
+msgstr "Zaakceptuj"
+
+#: templates/organizations/main.html:66
+msgid "Reject"
+msgstr "Odmów"
+
+#: templates/organizations/main.html:70
+msgid "Waiting for the owner of the organization to accept you as a member."
+msgstr "Czeka na akceptację właściciela organizacji."
+
+#: templates/organizations/main.html:80
+msgid "Make owner"
+msgstr "Ustaw jako właściciel"
+
+#: templates/organizations/new.html:6
+msgid "Create a new organization"
+msgstr "Utwórz nową organizację"
+
+#: templates/organizations/new.html:38
+msgid "Create organization"
+msgstr "Utwórz organizację"
+
+#: templates/organizations/organizations.html:7
+msgid "Organizations"
+msgstr "Organizacje"
+
+#: templates/organizations/snippets/organization.html:19
+#: templates/organizations/snippets/user.html:21
+msgid "Main projects and publications"
+msgstr "Główne projekty i publikacje"
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Membership',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('status', models.CharField(default='regular', max_length=10, choices=[('pending', 'Membership pending'), ('regular', 'Regular member'), ('owner', 'Organizaition owner')])),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ],
+ options={
+ 'ordering': ('user', 'organization'),
+ },
+ ),
+ migrations.CreateModel(
+ name='Organization',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=1024)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('logo', models.ImageField(upload_to='people/logo', blank=True)),
+ ('description', models.TextField(blank=True)),
+ ],
+ ),
+ migrations.AddField(
+ model_name='membership',
+ name='organization',
+ field=models.ForeignKey(to='organizations.Organization'),
+ ),
+ migrations.AddField(
+ model_name='membership',
+ name='user',
+ field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
+ ),
+ ]
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import jsonfield.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('organizations', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='organization',
+ name='description',
+ ),
+ migrations.AddField(
+ model_name='organization',
+ name='country',
+ field=models.CharField(blank=True, max_length=64, choices=[('intl', 'International'), ('de', 'Germany'), ('pl', 'Poland')]),
+ ),
+ migrations.AddField(
+ model_name='organization',
+ name='projects',
+ field=jsonfield.fields.JSONField(default=[]),
+ ),
+ migrations.AddField(
+ model_name='organization',
+ name='www',
+ field=models.URLField(blank=True),
+ ),
+ ]
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('organizations', '0002_auto_20150408_1513'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='organization',
+ name='country',
+ field=models.CharField(blank=True, max_length=64, choices=[('intl', 'International'), ('de', 'Germany'), ('gr', 'Greece'), ('pl', 'Poland')]),
+ ),
+ migrations.AlterField(
+ model_name='organization',
+ name='projects',
+ field=models.TextField(default='', blank=True),
+ ),
+ ]
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('organizations', '0003_auto_20150417_1551'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='organization',
+ name='preview_html',
+ field=models.TextField(default='', blank=True),
+ ),
+ migrations.AlterField(
+ model_name='organization',
+ name='country',
+ field=models.CharField(blank=True, max_length=64, choices=[('intl', 'International'), ('be', 'Belgium'), ('de', 'Germany'), ('gr', 'Greece'), ('pl', 'Poland')]),
+ ),
+ ]
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('organizations', '0004_auto_20150421_1114'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='organization',
+ name='description',
+ field=models.TextField(default='', blank=True),
+ ),
+ ]
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('auth', '0006_require_contenttypes_0002'),
+ ('organizations', '0005_organization_description'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='UserCard',
+ fields=[
+ ('logo', models.ImageField(upload_to='people/logo', blank=True)),
+ ('country', models.CharField(blank=True, max_length=64, choices=[('intl', 'International'), ('at', 'Austria'), ('be', 'Belgium'), ('gb', 'Bulgaria'), ('hr', 'Croatia'), ('cy', 'Cyprus'), ('cz', 'Czech Republic'), ('dk', 'Denmark'), ('ee', 'Estonia'), ('fi', 'Finland'), ('fr', 'France'), ('de', 'Germany'), ('gr', 'Greece'), ('hu', 'Hungary'), ('ie', 'Ireland'), ('it', 'Italy'), ('lv', 'Latvia'), ('lt', 'Lithuania'), ('lu', 'Luxembourg'), ('mt', 'Malta'), ('nl', 'Netherlands'), ('pl', 'Poland'), ('pt', 'Portugal'), ('ro', 'Romania'), ('sk', 'Slovakia'), ('si', 'Slovenia'), ('es', 'Spain'), ('se', 'Sweden'), ('uk', 'United Kingdom')])),
+ ('www', models.URLField(blank=True)),
+ ('description', models.TextField(default='', blank=True)),
+ ('projects', models.TextField(default='', blank=True)),
+ ('preview_html', models.TextField(default='', blank=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('user', models.ForeignKey(primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL, unique=True)),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ migrations.AlterField(
+ model_name='organization',
+ name='country',
+ field=models.CharField(blank=True, max_length=64, choices=[('intl', 'International'), ('at', 'Austria'), ('be', 'Belgium'), ('gb', 'Bulgaria'), ('hr', 'Croatia'), ('cy', 'Cyprus'), ('cz', 'Czech Republic'), ('dk', 'Denmark'), ('ee', 'Estonia'), ('fi', 'Finland'), ('fr', 'France'), ('de', 'Germany'), ('gr', 'Greece'), ('hu', 'Hungary'), ('ie', 'Ireland'), ('it', 'Italy'), ('lv', 'Latvia'), ('lt', 'Lithuania'), ('lu', 'Luxembourg'), ('mt', 'Malta'), ('nl', 'Netherlands'), ('pl', 'Poland'), ('pt', 'Portugal'), ('ro', 'Romania'), ('sk', 'Slovakia'), ('si', 'Slovenia'), ('es', 'Spain'), ('se', 'Sweden'), ('uk', 'United Kingdom')]),
+ ),
+ ]
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('organizations', '0006_auto_20150601_1606'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='organization',
+ name='preview_html_pl',
+ field=models.TextField(default='', blank=True),
+ ),
+ migrations.AddField(
+ model_name='usercard',
+ name='preview_html_pl',
+ field=models.TextField(default='', blank=True),
+ ),
+ ]
--- /dev/null
+from __future__ import unicode_literals
+
+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import User
+from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+from django.template.loader import render_to_string
+from django.utils import translation
+#from jsonfield import JSONField
+
+
+countries = [
+ ('intl', 'International'),
+ ('at', 'Austria'),
+ ('be', 'Belgium'),
+ ('gb', 'Bulgaria'),
+ ('hr', 'Croatia'),
+ ('cy', 'Cyprus'),
+ ('cz', 'Czech Republic'),
+ ('dk', 'Denmark'),
+ ('ee', 'Estonia'),
+ ('fi', 'Finland'),
+ ('fr', 'France'),
+ ('de', 'Germany'),
+ ('gr', 'Greece'),
+ ('hu', 'Hungary'),
+ ('ie', 'Ireland'),
+ ('it', 'Italy'),
+ ('lv', 'Latvia'),
+ ('lt', 'Lithuania'),
+ ('lu', 'Luxembourg'),
+ ('mt', 'Malta'),
+ ('nl', 'Netherlands'),
+ ('pl', 'Poland'),
+ ('pt', 'Portugal'),
+ ('ro', 'Romania'),
+ ('sk', 'Slovakia'),
+ ('si', 'Slovenia'),
+ ('es', 'Spain'),
+ ('se', 'Sweden'),
+ ('uk', 'United Kingdom'),
+]
+
+
+class Card(models.Model):
+ logo = models.ImageField(upload_to='people/logo', blank=True)
+ country = models.CharField(max_length=64, blank=True, choices=countries)
+ www = models.URLField(blank=True)
+ description = models.TextField(blank=True, default="")
+ projects = models.TextField(blank=True, default="")
+
+ preview_html = models.TextField(blank=True, default="")
+ preview_html_pl = models.TextField(blank=True, default="")
+
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ class Meta:
+ abstract = True
+
+ def save(self, *args, **kwargs):
+ translation.activate('en')
+ self.preview_html = render_to_string(self.preview_html_template, {
+ 'org': self
+ })
+ translation.activate('pl')
+ self.preview_html_pl = render_to_string(self.preview_html_template, {
+ 'org': self
+ })
+ ret = super(Card, self).save(*args, **kwargs)
+ return ret
+
+ def get_projects(self):
+ for project_line in self.projects.strip().split('\n'):
+ parts = project_line.strip().split(' ', 2)
+ if not parts or not parts[0]:
+ continue
+ url, lang, desc = (parts + [''] * 2)[:3]
+ yield url, lang, desc
+
+ def get_preview_html(self):
+ lang = translation.get_language()
+ try:
+ p = getattr(self, "preview_html_%s" % lang)
+ assert p
+ return p
+ except:
+ return self.preview_html
+
+@python_2_unicode_compatible
+class UserCard(Card):
+ preview_html_template = 'organizations/snippets/user.html'
+ user = models.ForeignKey(User, unique=True, primary_key=True)
+
+ def __str__(self):
+ return str(self.user)
+
+ def get_absolute_url(self):
+ return reverse('organizations_user', args=[self.user.pk])
+
+
+@python_2_unicode_compatible
+class Organization(Card):
+ preview_html_template = 'organizations/snippets/organization.html'
+
+ name = models.CharField(max_length=1024)
+ #logo = models.ImageField(upload_to='people/logo', blank=True)
+ #country = models.CharField(max_length=64, blank=True, choices=countries)
+ #www = models.URLField(blank=True)
+ #description = models.TextField(blank=True, default="")
+ ##projects = JSONField(default=[])
+ #projects = models.TextField(blank=True, default="")
+
+ #preview_html = models.TextField(blank=True, default="")
+
+ #created_at = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return self.name
+
+ def get_absolute_url(self):
+ return reverse("organizations_main", args=[self.pk])
+
+ def get_user_status(self, user):
+ if not user.is_authenticated():
+ return None
+ try:
+ member = self.membership_set.get(user=user)
+ except Membership.DoesNotExist:
+ return None
+ else:
+ return member.status
+
+ def is_member(self, user):
+ return self.get_user_status(user) in (
+ Membership.Status.OWNER, Membership.Status.REGULAR)
+
+ def is_owner(self, user):
+ return self.get_user_status(user) == Membership.Status.OWNER
+
+ def set_user_status(self, user, status):
+ try:
+ member = self.membership_set.get(user=user)
+ except Membership.DoesNotExist:
+ if status is not None:
+ self.membership_set.create(user=user, status=status)
+ else:
+ if status is not None:
+ member.status = status
+ member.save()
+ else:
+ member.delete()
+
+
+class Membership(models.Model):
+ class Status:
+ PENDING = 'pending'
+ REGULAR = 'regular'
+ OWNER = 'owner'
+
+ choices = (
+ (PENDING, 'Membership pending'),
+ (REGULAR, 'Regular member'),
+ (OWNER, 'Organizaition owner'),
+ )
+
+ organization = models.ForeignKey(Organization)
+ user = models.ForeignKey(User)
+ status = models.CharField(max_length=10, choices=Status.choices, default=Status.REGULAR)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ class Meta:
+ ordering = ('user', 'organization')
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+
+{% block inner_content %}
+
+ <h1>{% trans "Edit organization" %}</h1>
+
+
+ <form enctype="multipart/form-data" method="POST">
+ {% csrf_token %}
+ {{ form.non_field_errors }}
+ <label for="title">{% trans "Name" %}</label>
+ {{ form.name.errors }}
+ <input class="form-control" name="name" type="text" value="{{ form.name.value|default:"" }}">
+ <label for="logo">{% trans "Logo" %}</label>
+ {{ form.logo.errors }}
+ <div>
+ {{ form.logo }}
+ </div>
+ <label for="country">{% trans "Country" %}</label>
+ {{ form.country.errors }}
+ <select class="form-control" name="country">
+ {% for c, t in form.cts %}
+ <option value='{{ c }}' {% if form.country.value == c %}selected="selected"{% endif %}>{{ t }}</option>
+ {% endfor %}
+ </select>
+ <label for="www">{% trans "WWW" %}</label>
+ {{ form.www.errors }}
+ <input class="form-control" name="www" type="www" value="{{ form.www.value|default:"" }}">
+ <label for="title">{% trans "Description" %}</label>
+ {{ form.description.errors }}
+ <textarea class="form-control" name="description">{{ form.description.value|default:"" }}</textarea>
+ <label for="title">{% trans "Projects" %}</label>
+ {{ form.projects.errors }}
+ <textarea class="form-control" name="projects">{{ form.projects.value|default:"" }}</textarea>
+ {% trans "Enter each line as: URL language description" %}<br/>
+
+ <button style="margin-top:1em;" class="btn btn-default" type="submit">{% trans "Change" %}</button></td></tr>
+ </form>
+
+
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+
+{% block inner_content %}
+
+ <h1>{% trans "Edit user data" %}</h1>
+
+
+ <form enctype="multipart/form-data" method="POST">
+ {% csrf_token %}
+ {{ form.non_field_errors }}
+ <label for="first_name">{% trans "First name" %}</label>
+ {{ form.first_name.errors }}
+ <input class="form-control" name="first_name" type="text" value="{{ form.first_name.value|default:"" }}">
+ <label for="last_name">{% trans "Last name" %}</label>
+ {{ form.last_name.errors }}
+ <input class="form-control" name="last_name" type="text" value="{{ form.last_name.value|default:"" }}">
+
+ <label for="logo">{% trans "Photo" %}</label>
+ {{ form.logo.errors }}
+ <div>
+ {{ form.logo }}
+ </div>
+ <label for="country">{% trans "Country" %}</label>
+ {{ form.country.errors }}
+ <select class="form-control" name="country">
+ <option value=''>-</option>
+ {% for c, t in form.cts %}
+ <option value='{{ c }}' {% if form.country.value == c %}selected="selected"{% endif %}>{{ t }}</option>
+ {% endfor %}
+ </select>
+ <label for="www">{% trans "WWW" %}</label>
+ {{ form.www.errors }}
+ <input class="form-control" name="www" type="www" value="{{ form.www.value|default:"" }}">
+ <label for="title">{% trans "Description" %}</label>
+ {{ form.description.errors }}
+ <textarea class="form-control" name="description">{{ form.description.value|default:"" }}</textarea>
+ <label for="title">{% trans "Projects" %}</label>
+ {{ form.projects.errors }}
+ <textarea class="form-control" name="projects">{{ form.projects.value|default:"" }}</textarea>
+ {% trans "Enter each line as: URL language description" %}<br/>
+
+ <button style="margin-top:1em;" class="btn btn-default" type="submit">{% trans "Change" %}</button></td></tr>
+ </form>
+
+
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+
+{% block inner_content %}
+
+ <h1>{% trans "Join organization" %}</h1>
+
+ {% blocktrans with url=org.get_absolute_url %}
+ <p>Your membership in <strong><a href="{{ url }}">{{ org }}</a></strong> will have to be confirmed by
+ the organization owner.</p>
+ {% endblocktrans %}
+
+ <form enctype="multipart/form-data" method="POST">
+ {% csrf_token %}
+ <button style="margin-top:1em;" class="btn btn-default" type="submit">{% trans "Join" %}</button></td></tr>
+ </form>
+
+
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+{% load document_list from document_list %}
+
+{% block inner_content %}
+
+ {% if am_owner %}
+ <a style="float:right" class="btn btn-default" href="{% url 'organizations_edit' org.pk %}">{% trans "Edit" %}</a>
+ {% elif not my_status %}
+ <a style="float:right" class="btn btn-default" href="{% url 'organizations_join' org.pk %}">{% trans "Join" %}</a>
+ {% endif %}
+ {% if am_member %}
+ <a style="float:right; margin-right: 5px" class="btn btn-default" href="{% url 'catalogue_create_missing' %}?organization={{ org.pk }}">{% trans "New resource +" %}</a>
+ {% endif %}
+
+ {{ org.get_preview_html|safe }}
+
+ {% if tab == 'members' %}
+
+ <ul class="nav nav-tabs">
+ <li role="presentation"><a href="{% url 'organizations_main' org.pk %}">{% trans "Resources" %}</a></li>
+ <li role="presentation" class="active"><a href="#">{% trans "Members" %}</a></li>
+ </ul>
+
+
+ <table class="table table-striped">
+ <thead>
+ <th>{% trans "Name" %}</th>
+ {% if am_owner %}<th>{% trans "E-mail" %}</th>{% endif %}
+ <th>{% trans "Status" %}</th>
+ {% if am_owner %}<th></th>{% endif %}
+ </thead>
+ <tbody>
+
+
+ {% for m in org.membership_set.all %}
+ {% if am_owner or m.status != 'pending' or m.user == request.user %}
+ <tr class="member-{{ m.status }}">
+ <td>{{ m.user.first_name }} {{ m.user.last_name }}</td>
+ {% if am_owner %}<td>{{ m.user.email }}</td>{% endif %}
+ <td>
+ {% if m.status == 'owner' %}
+ {% trans "owner" %}
+ {% if am_owner %}
+ </td><td>
+ {% if not m.user == user %}
+ <form action="{% url 'organizations_membership' org.pk %}" method="POST">
+ {% csrf_token %}
+ <input type="hidden" name="user" value="{{ m.user.pk }}">
+ <div class="btn-group" role="group">
+ <button type="submit" name="action" value="regular" class="btn btn-info">{% trans "Make regular" %}</button>
+ <button type="submit" name="action" value="remove" class="btn btn-danger">{% trans "Remove" %}</button>
+ </div>
+ </form>
+ {% endif %}
+ {% endif %}
+ {% elif m.status == 'pending' %}
+ {% trans "pending" %}
+ {% if am_owner %}
+ </td><td>
+ <form action="{% url 'organizations_membership' org.pk %}" method="POST">
+ {% csrf_token %}
+ <input type="hidden" name="user" value="{{ m.user.pk }}">
+ <div class="btn-group" role="group">
+ <button type="submit" name="action" value="regular" class="btn btn-success">{% trans "Accept" %}</button>
+ <button type="submit" name="action" value="remove" class="btn btn-danger">{% trans "Reject" %}</button>
+ </div>
+ </form>
+ {% elif m.user == request.user %}
+ <br><small>{% trans "Waiting for the owner of the organization to accept you as a member." %}</small>
+ {% endif %}
+ {% else %}
+ regular
+ {% if am_owner %}
+ </td><td>
+ <form action="{% url 'organizations_membership' org.pk %}" method="POST">
+ {% csrf_token %}
+ <input type="hidden" name="user" value="{{ m.user.pk }}">
+ <div class="btn-group" role="group">
+ <button type="submit" name="action" value="owner" class="btn btn-warning">{% trans "Make owner" %}</button>
+ <button type="submit" name="action" value="remove" class="btn btn-danger">{% trans "Remove" %}</button>
+ </div>
+ </form>
+ {% endif %}
+ {% endif %}
+ </td>
+ </tr>
+ {% endif %}
+ {% endfor %}
+ </tbody>
+ </table>
+
+ {% elif tab == 'documents' %}
+
+ <ul class="nav nav-tabs">
+ <li role="presentation" class="active"><a href="">{% trans "Resources" %}</a></li>
+ <li role="presentation"><a href="{% url 'organizations_members' org.pk %}">{% trans "Members" %}</a></li>
+ </ul>
+
+ {% document_list organization=org %}
+
+ {% endif %}
+
+
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+
+{% block inner_content %}
+
+ <h1>{% trans "Create a new organization" %}</h1>
+
+
+ <form enctype="multipart/form-data" method="POST">
+ {% csrf_token %}
+ {{ form.non_field_errors }}
+ <label for="title">{% trans "Name" %}</label>
+ {{ form.name.errors }}
+ <input class="form-control" name="name" type="text" value="{{ form.name.value|default:"" }}">
+ <label for="logo">{% trans "Logo" %}</label>
+ {{ form.logo.errors }}
+ <div>
+ {{ form.logo }}
+ </div>
+ <label for="country">{% trans "Country" %}</label>
+ {{ form.country.errors }}
+ <select class="form-control" name="country">
+ {% for c, t in form.cts %}
+ <option value='{{ c }}' {% if form.country.value == c %}selected="selected"{% endif %}>{{ t }}</option>
+ {% endfor %}
+ </select>
+ <label for="www">{% trans "WWW" %}</label>
+ {{ form.www.errors }}
+ <input class="form-control" name="www" type="www" value="{{ form.www.value|default:"" }}">
+ <label for="title">{% trans "Description" %}</label>
+ {{ form.description.errors }}
+ <textarea class="form-control" name="description">{{ form.description.value|default:"" }}</textarea>
+ <label for="title">{% trans "Projects" %}</label>
+ {{ form.projects.errors }}
+ <textarea class="form-control" name="projects">{{ form.projects.value|default:"" }}</textarea>
+ {% trans "Enter each line as: URL language description" %}<br/>
+
+ <button style="margin-top:1em;" class="btn btn-default" type="submit">{% trans "Create organization" %}</button></td></tr>
+ </form>
+
+
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n thumbnail %}
+
+
+{% block inner_content %}
+
+<h1>{% trans "Organizations" %}</h1>
+{% for org in organizations %}
+ <div style="width: 182px; height: 180px; position: relative; border: 1px solid #aaa; margin: 10px; display: inline-block; padding: 0px; vertical-align: bottom">
+ <a style='display:block' href="{% url 'organizations_main' org.pk %}">
+ {% if org.logo %}
+ {% thumbnail org.logo "160x100" format="PNG" padding=True as th %}
+ <img src="{{ th.url }}" style="margin: 10px;">
+ {% endthumbnail %}
+ {% endif %}
+ <div style="position: absolute; bottom: 10px; left: 10px; width: 160px; height: 40px; overflow:hidden">{{ org.name }}</div>
+ </a>
+ </div>
+{% endfor %}
+
+{% endblock %}
--- /dev/null
+{% load thumbnail %}
+{% load i18n %}
+{% load urlinfo %}
+
+
+{% if org.logo %}
+<img style="margin: 2em 2em 0 0; float: left;" src="{% thumbnail org.logo '180x180' format="PNG" as th %}{{ th.url }}{% endthumbnail %}">
+{% endif %}
+
+<h1>{{ org }}</h1>
+ <table class="table table-striped" style="width: auto"><tbody>
+ <tr><th>{% trans "Country" %}:</th><td>{{ org.get_country_display }}</td></tr>
+ <tr><th>{% trans "WWW" %}:</th><td><a href="{{ org.www }}" target="_blank">{{ org.www }}</a></td></tr>
+
+ {% if org.description %}
+ <tr><th>{% trans "Description" %}:</th><td>{{ org.description }}</td></tr>
+ {% endif %}
+
+ <tr><th colspan="2">{% trans "Main projects and publications" %}:</th></tr>
+ {% for url, lang, desc in org.get_projects %}
+ {% urlinfo url as ui %}
+ <tr>
+ <td style="text-align: center">
+ {% if ui.picture_url %}
+ <a href="{{ url }}" target="_blank">
+ {% thumbnail ui.picture_url '120x40' format="PNG" as th %}
+ <img src="{{ th.url }}">
+ {% endthumbnail %}
+ </a>
+ {% endif %}
+ </td>
+ <td>{{ desc }}<br>
+ <a href="{{ url }}" target="_blank">{{ url }}</a> {% if lang %}({{ lang }}){% endif %}
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody></table>
--- /dev/null
+{% load thumbnail %}
+{% load i18n %}
+{% load urlinfo %}
+
+
+{% if org.logo %}
+<img style="margin: 2em 2em 0 0; float: left;" src="{% thumbnail org.logo '180x180' format="PNG" as th %}{{ th.url }}{% endthumbnail %}">
+{% endif %}
+
+<h1>{{ org.user.first_name }} {{ org.user.last_name }}</h1>
+ <table class="table table-striped" style="width: auto"><tbody>
+ {% if org.country %}<tr><th>{% trans "Country" %}:</th><td>{{ org.get_country_display }}</td></tr>{% endif %}
+ {% if org.www %}<tr><th>{% trans "WWW" %}:</th><td><a href="{{ org.www }}" target="_blank">{{ org.www }}</a></td></tr>{% endif %}
+
+ {% if org.description %}
+ <tr><th>{% trans "Description" %}:</th><td>{{ org.description }}</td></tr>
+ {% endif %}
+
+ {% for url, lang, desc in org.get_projects %}
+ {% if forloop.first %}
+ <tr><th colspan="2">{% trans "Main projects and publications" %}:</th></tr>
+ {% endif %}
+ {% urlinfo url as ui %}
+ <tr>
+ <td style="text-align: center">
+ {% if ui.picture_url %}
+ <a href="{{ url }}" target="_blank">
+ {% thumbnail ui.picture_url '120x40' format="PNG" as th %}
+ <img src="{{ th.url }}">
+ {% endthumbnail %}
+ </a>
+ {% endif %}
+ </td>
+ <td>{{ desc }}<br>
+ <a href="{{ url }}" target="_blank">{{ url }}</a> {% if lang %}({{ lang }}){% endif %}
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody></table>
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+{% load document_list from document_list %}
+
+{% block inner_content %}
+
+ {% if card.user.pk == request.user.pk %}
+ <a style="float:right" class="btn btn-default" href="{% url 'organizations_user_edit' %}">{% trans "Edit" %}</a>
+ {% endif %}
+
+ {{ card.get_preview_html|safe }}
+
+ <h2>{% trans "Resources" %}</h2>
+ {% document_list user=card.user.pk %}
+
+
+
+{% endblock %}
--- /dev/null
+from django import template
+from embeder import embed
+
+register = template.Library()
+
+@register.assignment_tag
+def urlinfo(url):
+ try:
+ return embed.get(url).get('global', {})
+ except:
+ return {}
--- /dev/null
+# -*- coding: utf-8
+from django.conf.urls import patterns, url
+from organizations import views
+
+
+urlpatterns = patterns('',
+ url(r'^$', views.organizations, name="organizations"),
+ url(r'^new/$', views.org_new, name="organizations_new"),
+ url(r'^(?P<pk>\d+)/$', views.main, name="organizations_main"),
+ url(r'^(?P<pk>\d+)/members/$', views.main, {'tab': 'members'}, name="organizations_members"),
+ url(r'^(?P<pk>\d+)/edit/$', views.edit, name="organizations_edit"),
+ url(r'^(?P<pk>\d+)/join/$', views.join, name="organizations_join"),
+ url(r'^(?P<pk>\d+)/membership/$', views.membership, name="organizations_membership"),
+
+ url(r'^people/(?P<pk>\d+)/$', views.user_card, name="organizations_user"),
+ url(r'^people/me/$', views.user_edit, name="organizations_user_edit"),
+)
--- /dev/null
+# Create your views here.
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth.models import User
+from django.shortcuts import render, redirect
+from django.http import HttpResponse, Http404
+#from django.views.decorators import require_post
+from .forms import OrganizationForm, UserCardForm
+from .models import Organization, Membership, UserCard
+
+@login_required
+def org_new(request):
+ if request.method == 'POST':
+ form = OrganizationForm(request.POST, request.FILES)
+ if form.is_valid():
+ org = form.save()
+ org.set_user_status(request.user, Membership.Status.OWNER)
+ return redirect(org.get_absolute_url())
+ else:
+ form = OrganizationForm()
+ return render(request, 'organizations/new.html', {'form': form})
+
+
+def main(request, pk, tab='documents'):
+ try:
+ org = Organization.objects.get(pk=pk)
+ except Organization.DoesNotExist:
+ raise Http404
+
+ my_status = org.get_user_status(request.user)
+ am_owner = my_status == Membership.Status.OWNER
+ am_member = am_owner or my_status == Membership.Status.REGULAR
+
+ return render(request, 'organizations/main.html', {
+ 'org': org,
+ 'tab': tab,
+ 'my_status': my_status,
+ 'am_owner': am_owner,
+ 'am_member': am_member,
+ })
+
+def user_card(request, pk):
+ try:
+ user = User.objects.get(pk=pk)
+ except User.DoesNotExist:
+ raise Http404
+ card, created = UserCard.objects.get_or_create(pk=user.pk)
+ return render(request, 'organizations/user_card.html', {
+ 'card': card,
+ })
+
+
+@login_required
+def edit(request, pk):
+ try:
+ org = Organization.objects.get(pk=pk)
+ except Organization.DoesNotExist:
+ raise Http404
+
+ if request.method == 'POST':
+ form = OrganizationForm(request.POST, request.FILES, instance=org)
+ if form.is_valid():
+ org = form.save()
+ return redirect(org.get_absolute_url())
+ else:
+ form = OrganizationForm(instance=org)
+ return render(request, 'organizations/edit.html', {'form': form})
+
+
+@login_required
+def user_edit(request):
+ card, created = UserCard.objects.get_or_create(pk=request.user.pk)
+ if request.method == 'POST':
+ form = UserCardForm(request.POST, request.FILES, instance=card)
+ if form.is_valid():
+ card = form.save()
+ return redirect(card.get_absolute_url())
+ else:
+ form = UserCardForm(instance=card)
+ return render(request, 'organizations/edit_user.html', {'form': form})
+
+
+@login_required
+def join(request, pk):
+ try:
+ org = Organization.objects.get(pk=pk)
+ except Organization.DoesNotExist:
+ raise Http404
+
+ if request.method == 'POST':
+ org.set_user_status(request.user, Membership.Status.PENDING)
+ return redirect('organizations_members', org.pk)
+
+ return render(request, 'organizations/join.html', {'org': org})
+
+@login_required
+#@POST_required
+def membership(request, pk):
+ try:
+ org = Organization.objects.get(pk=pk)
+ except Organization.DoesNotExist:
+ raise Http404
+
+ try:
+ user = User.objects.get(pk=request.POST['user'])
+ except KeyError, User.DoesNotExist:
+ pass
+ else:
+ if user != request.user:
+ action = request.POST.get('action')
+ if action:
+ if action == 'remove':
+ action = None
+ org.set_user_status(user, action)
+ return redirect('organizations_members', org.pk)
+
+
+def organizations(request):
+ return render(request, "organizations/organizations.html", {
+ 'organizations': Organization.objects.all(),
+ })
+++ /dev/null
-from django.contrib import admin
-
-from wiki import models
-
-
-class ThemeAdmin(admin.ModelAdmin):
- search_fields = ['name']
-
-admin.site.register(models.Theme, ThemeAdmin)
from django import forms
from django.utils.translation import ugettext_lazy as _
-from catalogue.models import Chunk
-
-
-class DocumentPubmarkForm(forms.Form):
- """
- Form for marking revisions for publishing.
- """
-
- id = forms.CharField(widget=forms.HiddenInput)
- publishable = forms.BooleanField(required=False, initial=True,
- label=_('Publishable'))
- revision = forms.IntegerField(widget=forms.HiddenInput)
+from catalogue.models import Document
+from catalogue.constants import STAGES
class DocumentTextSaveForm(forms.Form):
* parent_revision - revision which the modified text originated from.
* comment - user's verbose comment; will be used in commit.
- * stage_completed - mark this change as end of given stage.
+ * stage - change to this stage
"""
help_text=_(u"Describe changes you made."),
)
- stage_completed = forms.ModelChoiceField(
- queryset=Chunk.tag_model.objects.all(),
+ stage = forms.ChoiceField(
+ choices = [(s, s) for s in STAGES],
required=False,
- label=_(u"Completed"),
- help_text=_(u"If you completed a life cycle stage, select it."),
- )
-
- publishable = forms.BooleanField(required=False, initial=False,
- label=_('Publishable'),
- help_text=_(u"Mark this revision as publishable.")
+ label=_(u"Stage"),
+ help_text=_(u"If completed a work stage, change to another one."),
)
def __init__(self, *args, **kwargs):
label=_(u"Your comments"),
help_text=_(u"Describe the reason for reverting."),
)
+
+
+class DocumentTextPublishForm(forms.Form):
+ revision = forms.IntegerField(widget=forms.HiddenInput)
from datetime import datetime
from functools import wraps
+import json
from django import http
-from django.utils import simplejson as json
from django.utils.functional import Promise
def __init__(self, data={}, **kwargs):
# get rid of mimetype
- kwargs.pop('mimetype', None)
+ kwargs.pop('content_type', None)
data = json.dumps(data, cls=ExtendedEncoder)
- super(JSONResponse, self).__init__(data, mimetype="application/json", **kwargs)
+ super(JSONResponse, self).__init__(data, content_type="application/json", **kwargs)
# return errors
@wraps(view)
def authenticated_view(request, *args, **kwargs):
if not request.user.is_authenticated():
- return http.HttpResponse("Login required.", status=401, mimetype="text/plain")
+ return http.HttpResponse("Login required.", status=401, content_type="text/plain")
return view(request, *args, **kwargs)
return authenticated_view
@wraps(view)
def authorized_view(request, *args, **kwargs):
if not request.user.has_perm(permission):
- return http.HttpResponse("Access Forbidden.", status=403, mimetype="text/plain")
+ return http.HttpResponse("Access Forbidden.", status=403, content_type="text/plain")
return view(request, *args, **kwargs)
return authorized_view
return decorator
--- /dev/null
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Platforma Redakcyjna\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-03-21 14:34+0100\n"
+"PO-Revision-Date: 2013-07-10 16:58+0100\n"
+"Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
+"Language-Team: Fundacja Nowoczesna Polska <fundacja@nowoczesnapolska.org."
+"pl>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.4\n"
+
+#: forms.py:19 forms.py:63 views.py:279
+msgid "Publishable"
+msgstr "Gotowe do publikacji"
+
+#: forms.py:38 forms.py:89
+msgid "Author"
+msgstr "Autor"
+
+#: forms.py:39 forms.py:90
+msgid "Your name"
+msgstr "Imię i nazwisko"
+
+#: forms.py:44 forms.py:95
+msgid "Author's email"
+msgstr "E-mail autora"
+
+#: forms.py:45 forms.py:96
+msgid "Your email address, so we can show a gravatar :)"
+msgstr "Adres e-mail, żebyśmy mogli pokazać gravatar :)"
+
+#: forms.py:51 forms.py:102
+msgid "Your comments"
+msgstr "Twój komentarz"
+
+#: forms.py:52
+msgid "Describe changes you made."
+msgstr "Opisz swoje zmiany"
+
+#: forms.py:58
+msgid "Completed"
+msgstr "Ukończono"
+
+#: forms.py:59
+msgid "If you completed a life cycle stage, select it."
+msgstr "Jeśli został ukończony etap prac, wskaż go."
+
+#: forms.py:64
+msgid "Mark this revision as publishable."
+msgstr "Oznacz tę wersję jako gotową do publikacji."
+
+#: forms.py:103
+msgid "Describe the reason for reverting."
+msgstr "Opisz powód przywrócenia."
+
+#: models.py:14
+msgid "name"
+msgstr "nazwa"
+
+#: models.py:18
+msgid "theme"
+msgstr "motyw"
+
+#: models.py:19
+msgid "themes"
+msgstr "motywy"
+
+#: views.py:281
+msgid "Published"
+msgstr "Opublikowano"
+
+#: views.py:302
+msgid "Revision marked"
+msgstr "Wersja oznaczona"
+
+#: views.py:304
+msgid "Nothing changed"
+msgstr "Nic nie uległo zmianie"
+
+#: templates/admin/wiki/theme/change_list.html:22
+msgid "Table for Redmine wiki"
+msgstr "Tabela do wiki na Redmine"
+
+#: templates/wiki/diff_table.html:5
+msgid "Old version"
+msgstr "Stara wersja"
+
+#: templates/wiki/diff_table.html:6
+msgid "New version"
+msgstr "Nowa wersja"
+
+#: templates/wiki/document_details.html:32
+msgid "Click to open/close gallery"
+msgstr "Kliknij, aby (ro)zwinąć galerię"
+
+#: templates/wiki/document_details_base.html:33
+msgid "Help"
+msgstr "Pomoc"
+
+#: templates/wiki/document_details_base.html:35
+msgid "Version"
+msgstr "Wersja"
+
+#: templates/wiki/document_details_base.html:35
+msgid "Unknown"
+msgstr "nieznana"
+
+#: templates/wiki/document_details_base.html:37
+#: templates/wiki/pubmark_dialog.html:16
+msgid "Save"
+msgstr "Zapisz"
+
+#: templates/wiki/document_details_base.html:38
+msgid "Save attempt in progress"
+msgstr "Trwa zapisywanie"
+
+#: templates/wiki/document_details_base.html:39
+msgid "There is a newer version of this document!"
+msgstr "Istnieje nowsza wersja tego dokumentu!"
+
+#: templates/wiki/pubmark_dialog.html:17 templates/wiki/revert_dialog.html:40
+msgid "Cancel"
+msgstr "Anuluj"
+
+#: templates/wiki/revert_dialog.html:39
+msgid "Revert"
+msgstr "Przywróć"
+
+#: templates/wiki/tabs/annotations_view.html:9
+msgid "all"
+msgstr "wszystkie"
+
+#: templates/wiki/tabs/annotations_view_item.html:3
+msgid "Annotations"
+msgstr "Przypisy"
+
+#: templates/wiki/tabs/gallery_view.html:5
+msgid "Go to first image of this part"
+msgstr "Przejdź na początek"
+
+#: templates/wiki/tabs/gallery_view.html:8
+msgid "Previous"
+msgstr "Poprzednie"
+
+#: templates/wiki/tabs/gallery_view.html:13
+msgid "Next"
+msgstr "Następne"
+
+#: templates/wiki/tabs/gallery_view.html:16
+msgid "Zoom in"
+msgstr "Powiększ"
+
+#: templates/wiki/tabs/gallery_view.html:17
+msgid "Zoom out"
+msgstr "Zmniejsz"
+
+#: templates/wiki/tabs/gallery_view_item.html:3
+msgid "Gallery"
+msgstr "Galeria"
+
+#: templates/wiki/tabs/history_view.html:5
+msgid "Compare versions"
+msgstr "Porównaj wersje"
+
+#: templates/wiki/tabs/history_view.html:8
+msgid "Mark for publishing"
+msgstr "Oznacz do publikacji"
+
+#: templates/wiki/tabs/history_view.html:11
+msgid "Revert document"
+msgstr "Przywróć wersję"
+
+#: templates/wiki/tabs/history_view.html:14
+msgid "View version"
+msgstr "Zobacz wersję"
+
+#: templates/wiki/tabs/history_view_item.html:3
+msgid "History"
+msgstr "Historia"
+
+#: templates/wiki/tabs/search_view.html:3
+#: templates/wiki/tabs/search_view.html:5
+msgid "Search"
+msgstr "Szukaj"
+
+#: templates/wiki/tabs/search_view.html:8
+msgid "Replace with"
+msgstr "Zamień na"
+
+#: templates/wiki/tabs/search_view.html:10
+msgid "Replace"
+msgstr "Zamień"
+
+#: templates/wiki/tabs/search_view.html:12
+msgid "Replace all"
+msgstr "Zamień wszystko"
+
+msgid "Options"
+msgstr "Opcje"
+
+#: templates/wiki/tabs/search_view.html:15
+msgid "Case sensitive"
+msgstr "Rozróżniaj wielkość liter"
+
+#: templates/wiki/tabs/search_view.html:17
+msgid "From cursor"
+msgstr "Zacznij od kursora"
+
+#: templates/wiki/tabs/search_view_item.html:3
+msgid "Search and replace"
+msgstr "Znajdź i zamień"
+
+#: templates/wiki/tabs/source_editor_item.html:5
+msgid "Source code"
+msgstr "Kod źródłowy"
+
+#: templates/wiki/tabs/summary_view.html:13
+msgid "Refresh from working copy"
+msgstr "Odśwież z edytowanej wersji"
+
+#: templates/wiki/tabs/summary_view.html:17
+msgid "Title"
+msgstr "Tytuł"
+
+#: templates/wiki/tabs/summary_view.html:21
+msgid "Go to the book's page"
+msgstr "Przejdź do strony książki"
+
+#: templates/wiki/tabs/summary_view.html:24
+msgid "Document ID"
+msgstr "ID dokumentu"
+
+#: templates/wiki/tabs/summary_view.html:28
+msgid "Current version"
+msgstr "Aktualna wersja"
+
+#: templates/wiki/tabs/summary_view.html:31
+msgid "Last edited by"
+msgstr "Ostatnio edytowane przez"
+
+#: templates/wiki/tabs/summary_view.html:35
+msgid "Link to gallery"
+msgstr "Link do galerii"
+
+#: templates/wiki/tabs/summary_view.html:40
+msgid "Characters in document"
+msgstr "Znaków w dokumencie"
+
+#: templates/wiki/tabs/summary_view.html:41
+msgid "pages"
+msgstr "stron maszynopisu"
+
+#: templates/wiki/tabs/summary_view.html:41
+msgid "untagged"
+msgstr "nieotagowane"
+
+#: templates/wiki/tabs/summary_view_item.html:3
+msgid "Summary"
+msgstr "Podsumowanie"
+
+#: templates/wiki/tabs/wysiwyg_editor.html:9
+msgid "Insert theme"
+msgstr "Wstaw motyw"
+
+#: templates/wiki/tabs/wysiwyg_editor.html:12
+msgid "Insert annotation"
+msgstr "Wstaw przypis"
+
+#: templates/wiki/tabs/wysiwyg_editor_item.html:3
+msgid "Visual editor"
+msgstr "Edytor wizualny"
+
+#: templates/wiki/bootstrap.html:95
+msgid "Informations about lesson"
+msgstr "Informacje o lekcji"
+
+#: templates/wiki/bootstrap.html:97
+msgid "Stage"
+msgstr "Etap"
+
+#: templates/wiki/bootstrap.html:98
+msgid "Assignment"
+msgstr "Przypisano"
+
+#~ msgid "ZIP file"
+#~ msgstr "Plik ZIP"
+
+#~ msgid "Chunk with this slug already exists"
+#~ msgstr "Część z tym slugiem już istnieje"
+
+#~ msgid "Append to"
+#~ msgstr "Dołącz do"
+
+#~ msgid "title"
+#~ msgstr "tytuł"
+
+#~ msgid "scan gallery name"
+#~ msgstr "nazwa galerii skanów"
+
+#~ msgid "parent"
+#~ msgstr "rodzic"
+
+#~ msgid "parent number"
+#~ msgstr "numeracja rodzica"
+
+#~ msgid "book"
+#~ msgstr "książka"
+
+#~ msgid "books"
+#~ msgstr "książki"
+
+#~ msgid "Slug already used for %s"
+#~ msgstr "Slug taki sam jak dla pliku %s"
+
+#~ msgid "Slug already used in repository."
+#~ msgstr "Dokument o tym slugu już istnieje w repozytorium."
+
+#~ msgid "File should be UTF-8 encoded."
+#~ msgstr "Plik powinien mieć kodowanie UTF-8."
+
+#~ msgid "Tag added"
+#~ msgstr "Dodano tag"
+
+#~ msgid "Append book"
+#~ msgstr "Dołącz książkę"
+
+#~ msgid "edit"
+#~ msgstr "edytuj"
+
+#~ msgid "add basic document structure"
+#~ msgstr "dodaj podstawową strukturę dokumentu"
+
+#~ msgid "change master tag to"
+#~ msgstr "zmień tak master na"
+
+#~ msgid "add begin trimming tag"
+#~ msgstr "dodaj początkowy ogranicznik"
+
+#~ msgid "add end trimming tag"
+#~ msgstr "dodaj końcowy ogranicznik"
+
+#~ msgid "unstructured text"
+#~ msgstr "tekst bez struktury"
+
+#~ msgid "unknown XML"
+#~ msgstr "nieznany XML"
+
+#~ msgid "broken document"
+#~ msgstr "uszkodzony dokument"
+
+#~ msgid "Apply fixes"
+#~ msgstr "Wykonaj zmiany"
+
+#~ msgid "Append to other book"
+#~ msgstr "Dołącz do innej książki"
+
+#~ msgid "Last published"
+#~ msgstr "Ostatnio opublikowano"
+
+#~ msgid "Full XML"
+#~ msgstr "Pełny XML"
+
+#~ msgid "HTML version"
+#~ msgstr "Wersja HTML"
+
+#~ msgid "TXT version"
+#~ msgstr "Wersja TXT"
+
+#~ msgid "EPUB version"
+#~ msgstr "Wersja EPUB"
+
+#~ msgid "PDF version"
+#~ msgstr "Wersja PDF"
+
+#~ msgid "This book cannot be published yet"
+#~ msgstr "Ta książka nie może jeszcze zostać opublikowana"
+
+#~ msgid "Add chunk"
+#~ msgstr "Dodaj część"
+
+#~ msgid "Clear filter"
+#~ msgstr "Wyczyść filtr"
+
+#~ msgid "No books found."
+#~ msgstr "Nie znaleziono książek."
+
+#~ msgid "Your last edited documents"
+#~ msgstr "Twoje ostatnie edycje"
+
+#~ msgid "Bulk documents upload"
+#~ msgstr "Hurtowe dodawanie dokumentów"
+
+#~ msgid ""
+#~ "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with "
+#~ "<code>.xml</code> will be ignored."
+#~ msgstr ""
+#~ "Proszę wskazać archiwum ZIP z plikami XML w kodowaniu UTF-8. Pliki nie "
+#~ "kończące się na <code>.xml</code> zostaną zignorowane."
+
+#~ msgid "Upload"
+#~ msgstr "Załaduj"
+
+#~ msgid ""
+#~ "There have been some errors. No files have been added to the repository."
+#~ msgstr "Wystąpiły błędy. Żadne pliki nie zostały dodane do repozytorium."
+
+#~ msgid "Offending files"
+#~ msgstr "Błędne pliki"
+
+#~ msgid "Correct files"
+#~ msgstr "Poprawne pliki"
+
+#~ msgid "Files have been successfully uploaded to the repository."
+#~ msgstr "Pliki zostały dodane do repozytorium."
+
+#~ msgid "Uploaded files"
+#~ msgstr "Dodane pliki"
+
+#~ msgid "Skipped files"
+#~ msgstr "Pominięte pliki"
+
+#~ msgid "Files skipped due to no <code>.xml</code> extension"
+#~ msgstr "Pliki pominięte z powodu braku rozszerzenia <code>.xml</code>."
+
+#~ msgid "Users"
+#~ msgstr "Użytkownicy"
+
+#~ msgid "Unassigned"
+#~ msgstr "Nie przypisane"
+
+#~ msgid "All"
+#~ msgstr "Wszystkie"
+
+#~ msgid "Add"
+#~ msgstr "Dodaj"
+
+#~ msgid "Admin"
+#~ msgstr "Administracja"
+
+#~ msgid "First correction"
+#~ msgstr "Autokorekta"
+
+#~ msgid "Tagging"
+#~ msgstr "Tagowanie"
+
+#~ msgid "Initial Proofreading"
+#~ msgstr "Korekta"
+
+#~ msgid "Annotation Proofreading"
+#~ msgstr "Sprawdzenie przypisów źródła"
+
+#~ msgid "Modernisation"
+#~ msgstr "Uwspółcześnienie"
+
+#~ msgid "Themes"
+#~ msgstr "Motywy"
+
+#~ msgid "Editor's Proofreading"
+#~ msgstr "Ostateczna redakcja literacka"
+
+#~ msgid "Technical Editor's Proofreading"
+#~ msgstr "Ostateczna redakcja techniczna"
+
+#~ msgid "Finished stage: %s"
+#~ msgstr "Ukończony etap: %s"
+
+#~ msgid "Refresh"
+#~ msgstr "Odśwież"
+
+#~ msgid "Insert special character"
+#~ msgstr "Wstaw znak specjalny"
--- /dev/null
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-03-21 14:43+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
+"|| n%100>=20) ? 1 : 2);\n"
+
+#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:2759
+#: static/wiki/editor/src/editor/modules/data/data.js:13
+msgid "This is an empty document."
+msgstr "To jest pusty dokument."
+
+#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:2773
+#: static/wiki/editor/src/editor/modules/data/data.js:27
+msgid "The content of this document seems to be invalid - only XML source editing will be possible. :("
+msgstr "Wygląda na to, że dokument zawiera błędy - możliwa będzie jedynie edycja źródła. :("
+
+#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:2838
+#: static/wiki/editor/src/editor/modules/data/data.js:92
+msgid "Save Document"
+msgstr "Zapisz dokument"
+
+#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:2839
+#: static/wiki/editor/src/editor/modules/data/data.js:93
+msgid "Save"
+msgstr "Zapisz"
+
+#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:2894
+#: static/wiki/editor/src/editor/modules/data/data.js:148
+msgid "Restore Version"
+msgstr "Przywracanie wersji"
+
+#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:2895
+#: static/wiki/editor/src/editor/modules/data/data.js:149
+msgid "Restore"
+msgstr "Przywróć"
+
+#: static/wiki/editor/src/editor/modules/data/document.js:28
+msgid "Error"
+msgstr "Błąd"
+#: static/wiki/editor/src/editor/modules/documentCanvas/commands.js:141
+msgid "Create link"
+msgstr "Utwórz link"
+
+#: static/wiki/editor/src/editor/modules/documentCanvas/commands.js:142
+#: static/wiki/editor/src/editor/modules/documentCanvas/commands.js:224
+msgid "Apply"
+msgstr "Zatwierdź"
+
+#: static/wiki/editor/src/editor/modules/data/document.js:29
+msgid "Something wrong happend when applying this change so it was undone."
+msgstr "Coś poszło nie tak podczas wprowadzania tej zmiany, więc została ona cofnięta."
+#: static/wiki/editor/src/editor/modules/documentCanvas/commands.js:145
+#: static/wiki/editor/src/editor/modules/documentCanvas/commands.js:227
+msgid "Link"
+msgstr "Link"
+
+#: static/wiki/editor/src/editor/modules/documentCanvas/commands.js:223
+msgid "Edit link"
+msgstr "Edytuj link"
+
+#: static/wiki/editor/src/editor/modules/mainBar/template.html:5
+msgid "Exit"
+msgstr "Wyjdź"
+
+#: static/wiki/editor/src/editor/modules/nodePane/template.html:3
+msgid "Current node"
+msgstr "Bieżący węzeł"
+#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:3172
+#: static/wiki/editor/src/editor/modules/rng/rng.js:73
+msgid "Editor"
+msgstr "Edytor"
+
+#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:3173
+#: static/wiki/editor/build/rng.js:3188
+#: static/wiki/editor/src/editor/modules/rng/rng.js:74
+#: static/wiki/editor/src/editor/modules/rng/rng.js:89
+msgid "Source"
+msgstr "Źródło"
+
+#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:3174
+#: static/wiki/editor/src/editor/modules/rng/rng.js:75
+msgid "History"
+msgstr "Historia"
+
+#: static/wiki/editor/src/editor/modules/documentCanvas/canvas/keyboard.js:61
+#: static/wiki/editor/src/editor/modules/documentCanvas/canvas/keyboard.js:86
+#: static/wiki/editor/src/editor/modules/documentCanvas/canvas/keyboard.js:124
+msgid "Splitting text"
+msgstr "Rozbicie tekstu"
+#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:3212
+#: static/wiki/editor/src/editor/modules/rng/rng.js:113
+msgid "Saving..."
+msgstr "Zapisywanie..."
+
+#: static/wiki/editor/src/editor/modules/documentCanvas/canvas/keyboard.js:309
+msgid "Remove text"
+msgstr "Usuń tekst"
+#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:3223
+#: static/wiki/editor/src/editor/modules/rng/rng.js:124
+msgid "Restoring version "
+msgstr "Przywracanie wersji"
+
+#: static/wiki/editor/src/editor/modules/documentToolbar/actionView.js:49
+#: static/wiki/editor/src/editor/modules/statusBar/statusBar.js:28
+msgid "error :("
+msgstr "błąd :("
+#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:3353
+#: static/wiki/editor/src/editor/modules/rng/rng.js:247
+msgid "editor"
+msgstr "edytor"
+
+#: static/wiki/editor/build/rng.js:3364
+#: static/wiki/editor/src/editor/modules/rng/rng.js:273
+msgid "Do you really want to exit?"
+msgstr "Czy na pewno chcesz zakończyć pracę?"
+
+#: static/wiki/editor/build/rng.js:3366
+#: static/wiki/editor/src/editor/modules/rng/rng.js:275
+msgid "Document contains unsaved changes!"
+msgstr "Dokument zawiera niezapisane zmiany"
+
+#: static/wiki/editor/src/editor/modules/data/data.js:137
+msgid "Local draft of a document exists"
+msgstr "Istnieje kopia lokalna dokumentu"
+
+#: static/wiki/editor/src/editor/modules/data/data.js:138
+msgid ""
+"Unsaved local draft of this version of the document exists in your browser. "
+"Do you want to load it instead?"
+msgstr ""
+"Twoja przeglądarka posiada niezapisaną jeszcze na serwerze lokalną kopię tej "
+"wersji dokumentu. Czy chcesz jej teraz użyć?"
+
+#: static/wiki/editor/src/editor/modules/data/data.js:139
+msgid "Yes, restore local draft"
+msgstr "Tak, chcę użyć lokalną kopię"
+
+#: static/wiki/editor/src/editor/modules/data/data.js:140
+msgid "No, use version loaded from the server"
+msgstr "Nie, chcę załadować wersję z serwera"
+
+#: static/wiki/editor/src/editor/modules/data/dialog.js:30
+msgid "Submit"
+msgstr "Zatwierdź"
+
+#: static/wiki/editor/src/editor/modules/data/dialog.js:31
+msgid "Cancel"
+msgstr "Anuluj"
+
+#: static/wiki/editor/src/editor/modules/mainBar/mainBar.js:15
+msgid "anonymous"
+msgstr "anonim"
+
+#: static/wiki/editor/src/editor/modules/rng/rng.js:119
+msgid "Saving document"
+msgstr "Zapisywanie dokumentu"
+
+#: static/wiki/editor/src/editor/modules/rng/rng.js:120
+msgid "Saving local copy"
+msgstr "Zapisywanie kopii lokalnej"
+
+#: static/wiki/editor/src/editor/modules/rng/rng.js:128
+msgid "Document saved"
+msgstr "Dokument zapisany"
+
+#: static/wiki/editor/src/editor/modules/rng/rng.js:129
+msgid "Local copy saved"
+msgstr "Wersja robocza zapisana"
+
+msgid "Draft Saved"
+msgstr "Zapisana wersja robocza"
+
+msgid "no draft exists"
+msgstr "brak wersji roboczej"
+
+msgid "drop a working draft"
+msgstr "porzuć wersję roboczą"
+
+#: static/wiki/editor/src/editor/modules/documentCanvas/canvas/canvas.js:45
+msgid "Changing text"
+msgstr "Edycja tekstu"
+
+#: static/wiki/editor/src/editor/modules/metadataEditor/metadataEditor.js:26
+msgid "Add metadata row"
+msgstr "Dodaj wiersz metadanych"
+
+#: static/wiki/editor/src/editor/modules/metadataEditor/metadataEditor.js:32
+msgid "Remove metadata row"
+msgstr "Usuń wiersz metadanych"
+
+#: static/wiki/editor/src/editor/modules/statusBar/statusBar.js:33
+msgid "Undescribed action"
+msgstr "Działanie nieopisane"
+#: static/wiki/editor/src/editor/modules/metadataEditor/metadataEditor.js:59
+msgid "Metadata edit"
+msgstr "Edycja metadanych"
+
+#: static/wiki/editor/src/editor/modules/statusBar/statusBar.js:22
+msgid "Action not allowed"
+msgstr "Działanie niedozwolone"
+
+#: static/wiki/editor/src/editor/plugins/core/core.js:63
+msgid "Undo"
+msgstr "Cofnij"
+
+#: static/wiki/editor/src/editor/plugins/core/core.js:63
+msgid "Redo"
+msgstr "Powtórz"
+
+#: static/wiki/editor/src/editor/plugins/core/core.js:64
+msgid "There is nothing to undo"
+msgstr "Nie ma nic więcej do cofnięcia"
+
+#: static/wiki/editor/src/editor/plugins/core/core.js:64
+msgid "There is nothing to redo"
+msgstr "Nie ma nic więcej do powtórzenia"
+
+#: static/wiki/editor/src/editor/plugins/core/core.js:66
+msgid "unknown operation"
+msgstr "Nieznana operacja"
+
+#: static/wiki/editor/src/editor/plugins/core/core.js:126
+msgid "Insert comment"
+msgstr "Wstaw komentarz"
+
+#: static/wiki/editor/src/editor/plugins/core/core.js:148
+msgid "Hide grid"
+msgstr "Schowaj siatkę"
+
+#: static/wiki/editor/src/editor/plugins/core/core.js:148
+msgid "Show grid"
+msgstr "Pokaż siatkę"
+#: static/wiki/editor/src/editor/plugins/core/core.js:293
+msgid "link"
+msgstr "link"
+
+#: static/wiki/editor/src/editor/plugins/core/footnote.js:47
+#: static/wiki/editor/src/editor/plugins/core/core.js:306
+msgid "Create link from selection"
+msgstr "Utwórz link z zaznaczonego tekstu"
+
+#: static/wiki/editor/src/editor/plugins/core/core.js:325
+msgid "Mark as emphasized"
+msgstr "Oznacz jako wyróżnione"
+
+#: static/wiki/editor/src/editor/plugins/core/core.js:365
+msgid "Remove emphasis"
+msgstr "Usuń wyróżnienie"
+
+#: static/wiki/editor/src/editor/plugins/core/core.js:366
+#: static/wiki/editor/src/editor/plugins/core/core.js:326
+msgid "Mark as citation"
+msgstr "Oznacz jako cytat"
+
+#: static/wiki/editor/src/editor/plugins/core/core.js:366
+msgid "Remove citation"
+msgstr "Usuń cytat"
+
+msgid "Create footnote from selection"
+msgstr "Utwórz przypis z zaznaczonego tekstu"
+
+#: static/wiki/editor/src/editor/plugins/core/footnote.js:54
+msgid "Insert footnote after cursor"
+msgstr "Wstaw przypis za kursorem"
+
+#: static/wiki/editor/src/editor/plugins/core/footnote.js:62
+msgid "Cannot insert footnote after root node"
+msgstr "Nie można wstawić przypisu za węzłem głównym"
+
+#: static/wiki/editor/src/editor/plugins/core/footnote.js:67
+msgid "Insert footnote after node"
+msgstr "Wstaw przypis za węzłem"
+
+#: static/wiki/editor/src/editor/plugins/core/lists.js:112
+msgid "bull. list"
+msgstr "lista pkt."
+#: static/wiki/editor/src/editor/plugins/core/insert.js:60
+msgid "Missing tag name"
+msgstr "Nie wybrano tagu"
+
+#: static/wiki/editor/src/editor/plugins/core/lists.js:112
+msgid "num. list"
+msgstr "lista num."
+#: static/wiki/editor/src/editor/plugins/core/insert.js:65
+#, c-format
+msgid "Wrap text with %s"
+msgstr "Obejmij tekst przez %s"
+
+#: static/wiki/editor/src/editor/plugins/core/insert.js:71
+#, c-format
+msgid "Wrap nodes with %s"
+msgstr "Obejmij węzły przez %s"
+
+#: static/wiki/editor/src/editor/plugins/core/insert.js:78
+#, c-format
+msgid "Wrap current node with %s"
+msgstr "Obejmij bieżący węzeł przez %s"
+
+#: static/wiki/editor/src/editor/plugins/core/insert.js:85
+msgid "Cannot insert after root node"
+msgstr "Nie można wstawić węzła za węzłem głównym"
+
+#: static/wiki/editor/src/editor/plugins/core/insert.js:90
+#, c-format
+msgid "Insert %s after current"
+msgstr "Wstaw %s za bieżący węzeł"
+
+#: static/wiki/editor/src/editor/plugins/core/lists.js:91
+#, c-format
+msgid "Change list type to %s"
+msgstr "Zmień typ listy na %s"
+
+#: static/wiki/editor/src/editor/plugins/core/lists.js:98
+msgid "Remove list"
+msgstr "Usuń listę"
+
+#: static/wiki/editor/src/editor/plugins/core/lists.js:107
+#, c-format
+msgid "Make %s fragment(s) into list"
+msgstr "Stwórz listę z %s fragmentów"
+
+#: static/wiki/editor/src/editor/plugins/core/switch.js:68
+msgid "header"
+msgstr "nagłówek"
+#: static/wiki/editor/src/editor/plugins/core/lists.js:122
+#: static/wiki/editor/src/editor/plugins/core/lists.js:123
+msgid "list item up"
+msgstr "Element listy do góry"
+
+#: static/wiki/editor/src/editor/plugins/core/remove.js:21
+msgid "Unwrap with siblings"
+msgstr "Odpakuj wraz sąsiadami"
+
+#: static/wiki/editor/src/editor/plugins/core/remove.js:21
+msgid "Cannot unwrap children of a root node"
+msgstr "Nie można odpakować dzieci głównego węzła"
+
+#: static/wiki/editor/src/editor/plugins/core/remove.js:49
+msgid "Cannot remove root node"
+msgstr "Nie można usunąć głównego węzła"
+
+#: static/wiki/editor/src/editor/plugins/core/switch.js:69
+msgid "paragraf"
+msgstr "akapit"
+#: static/wiki/editor/src/editor/plugins/core/remove.js:54
+msgid "Remove node"
+msgstr "Usuń węzeł"
+
+#: static/wiki/editor/src/editor/plugins/core/templates.js:27
+msgid "No template selected"
+msgstr "Nie wybrano template'u"
+
+#: static/wiki/editor/src/editor/plugins/core/templates.js:32
+msgid "Wrong node selected"
+msgstr "Wybrany niepoprawny węzeł"
+
+#: static/wiki/editor/src/editor/plugins/core/templates.js:37
+#, c-format
+msgid "Insert template %s"
+msgstr "Wstaw template %s"
+
+msgid "Switch to"
+msgstr "Zamień na"
+
+msgid "change"
+msgstr "zmień"
+
+msgid "remove"
+msgstr "usuń"
+
+msgid "Remove link"
+msgstr "Usuń link"
+
+msgid "edit"
+msgstr "edytuj"
+
+msgid "Delete"
+msgstr "Usuń"
+
+msgid "Respond"
+msgstr "Odpowiedz"
+
+msgid "Comment"
+msgstr "Skomentuj"
+
+msgid "Delete this comment?"
+msgstr "Usunąć ten komentarz?"
+
+msgid "Metadata"
+msgstr "Metadane"
+
+msgid "Document Metadata"
+msgstr "Metadane dokumentu"
+
+msgid "Close"
+msgstr "Zamknij"
+
+msgid "Exercise"
+msgstr "Zadanie"
+
+msgid "Insert exercise"
+msgstr "Wstaw zadanie"
+
+msgid "Single Choice"
+msgstr "Wybór jednokrotny"
+
+msgid "Multiple Choice"
+msgstr "Wybór wielokrotny"
+
+msgid "Gaps"
+msgstr "Luki"
+
+msgid "Replace"
+msgstr "Zastąp"
+
+msgid "Order"
+msgstr "Uporządkuj"
+
+msgid "True or False"
+msgstr "Prawda lub fałsz"
+
+msgid "True"
+msgstr "Prawda"
+
+msgid "False"
+msgstr "Fałsz"
+
+msgid "Mark to replace"
+msgstr "Oznacz do zastąpienia"
+
+msgid "Edit replace mark"
+msgstr "Edytuj zastąpienie"
+
+msgid "Remove replace mark"
+msgstr "Usuń zastąpienie"
+
+msgid "Create a gap"
+msgstr "Stwórz lukę"
+
+msgid "Remove a gap"
+msgstr "Usuń lukę"
+
+msgid "Description goes here"
+msgstr "Tu wpisz polecenie"
+
+msgid "Removing exercise"
+msgstr "Usuwanie zadania"
+
+msgid "Do you really want to remove this exercise?"
+msgstr "Czy na pewno chcesz usunąć to zadanie?"
+
+msgid "Yes"
+msgstr "Tak"
+
+msgid "No, don't do anything!"
+msgstr "Nie, nic nie rób!"
+
+msgid "Edit text to replace with"
+msgstr "Edytuj tekst zastępujący"
+
+msgid "Initial"
+msgstr "Inicjalnie"
+
+msgid "Solution"
+msgstr "Rozwiązanie"
+
+msgid "Change solution"
+msgstr "Zmień rozwiązanie"
+
+msgid "First item"
+msgstr "Pierwszy element"
\ No newline at end of file
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
+#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: Platforma Redakcyjna\n"
+"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-03-21 14:34+0100\n"
-"PO-Revision-Date: 2013-07-10 16:58+0100\n"
-"Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
-"Language-Team: Fundacja Nowoczesna Polska <fundacja@nowoczesnapolska.org."
-"pl>\n"
+"POT-Creation-Date: 2016-01-26 10:40+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 1.5.4\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
+"|| n%100>=20) ? 1 : 2);\n"
-#: forms.py:19 forms.py:63 views.py:279
-msgid "Publishable"
-msgstr "Gotowe do publikacji"
-
-#: forms.py:38 forms.py:89
+#: forms.py:28 forms.py:74
msgid "Author"
msgstr "Autor"
-#: forms.py:39 forms.py:90
+#: forms.py:29 forms.py:75
msgid "Your name"
-msgstr "Imię i nazwisko"
+msgstr "Twoje imię i nazwisko"
-#: forms.py:44 forms.py:95
+#: forms.py:34 forms.py:80
msgid "Author's email"
msgstr "E-mail autora"
-#: forms.py:45 forms.py:96
+#: forms.py:35 forms.py:81
msgid "Your email address, so we can show a gravatar :)"
-msgstr "Adres e-mail, żebyśmy mogli pokazać gravatar :)"
+msgstr "Twój e-mail, do pokazania gravatara :)"
-#: forms.py:51 forms.py:102
+#: forms.py:41 forms.py:87
msgid "Your comments"
-msgstr "Twój komentarz"
+msgstr "Twoje komentarze"
-#: forms.py:52
+#: forms.py:42
msgid "Describe changes you made."
msgstr "Opisz swoje zmiany"
-#: forms.py:58
-msgid "Completed"
-msgstr "Ukończono"
-
-#: forms.py:59
-msgid "If you completed a life cycle stage, select it."
-msgstr "Jeśli został ukończony etap prac, wskaż go."
+#: forms.py:48 templates/wiki/bootstrap.html:72
+msgid "Stage"
+msgstr "Etap"
-#: forms.py:64
-msgid "Mark this revision as publishable."
-msgstr "Oznacz tę wersję jako gotową do publikacji."
+#: forms.py:49
+msgid "If completed a work stage, change to another one."
+msgstr "Jeśli etap pracy został zakończony, zmień go na inny."
-#: forms.py:103
+#: forms.py:88
msgid "Describe the reason for reverting."
-msgstr "Opisz powód przywrócenia."
-
-#: models.py:14
-msgid "name"
-msgstr "nazwa"
-
-#: models.py:18
-msgid "theme"
-msgstr "motyw"
-
-#: models.py:19
-msgid "themes"
-msgstr "motywy"
-
-#: views.py:281
-msgid "Published"
-msgstr "Opublikowano"
-
-#: views.py:302
-msgid "Revision marked"
-msgstr "Wersja oznaczona"
-
-#: views.py:304
-msgid "Nothing changed"
-msgstr "Nic nie uległo zmianie"
+msgstr "Podaj powód wycofania zmian."
#: templates/admin/wiki/theme/change_list.html:22
msgid "Table for Redmine wiki"
msgstr "Tabela do wiki na Redmine"
+#: templates/wiki/bootstrap.html:69
+msgid "Informations about lesson"
+msgstr "Informacje o lekcji"
+
+#: templates/wiki/bootstrap.html:71
+#: templates/wiki/document_details_base.html:35
+msgid "Version"
+msgstr "Wersja"
+
+#: templates/wiki/bootstrap.html:73
+msgid "Assignment"
+msgstr "Przypisanie"
+
#: templates/wiki/diff_table.html:5
msgid "Old version"
msgstr "Stara wersja"
#: templates/wiki/document_details.html:32
msgid "Click to open/close gallery"
-msgstr "Kliknij, aby (ro)zwinąć galerię"
+msgstr "Kliknij, by otworzyć/zamknąć galerię"
#: templates/wiki/document_details_base.html:33
msgid "Help"
msgstr "Pomoc"
-#: templates/wiki/document_details_base.html:35
-msgid "Version"
-msgstr "Wersja"
-
#: templates/wiki/document_details_base.html:35
msgid "Unknown"
-msgstr "nieznana"
+msgstr "Nieznany"
#: templates/wiki/document_details_base.html:37
#: templates/wiki/pubmark_dialog.html:16
#: templates/wiki/document_details_base.html:38
msgid "Save attempt in progress"
-msgstr "Trwa zapisywanie"
+msgstr "Trwa próba zapisu"
#: templates/wiki/document_details_base.html:39
msgid "There is a newer version of this document!"
-msgstr "Istnieje nowsza wersja tego dokumentu!"
+msgstr "Istnieje nowsza wersja tego dokumentu"
#: templates/wiki/pubmark_dialog.html:17 templates/wiki/revert_dialog.html:40
msgid "Cancel"
#: templates/wiki/revert_dialog.html:39
msgid "Revert"
-msgstr "Przywróć"
+msgstr "Wycofaj"
#: templates/wiki/tabs/annotations_view.html:9
msgid "all"
#: templates/wiki/tabs/gallery_view.html:5
msgid "Go to first image of this part"
-msgstr "Przejdź na początek"
+msgstr "Idź do pierwszego obrazka dla tej części"
#: templates/wiki/tabs/gallery_view.html:8
msgid "Previous"
-msgstr "Poprzednie"
+msgstr "Poprzedni"
#: templates/wiki/tabs/gallery_view.html:13
msgid "Next"
-msgstr "Następne"
+msgstr "Następny"
#: templates/wiki/tabs/gallery_view.html:16
msgid "Zoom in"
#: templates/wiki/tabs/gallery_view.html:17
msgid "Zoom out"
-msgstr "Zmniejsz"
+msgstr "Pomniejsz"
#: templates/wiki/tabs/gallery_view_item.html:3
msgid "Gallery"
#: templates/wiki/tabs/history_view.html:11
msgid "Revert document"
-msgstr "Przywróć wersję"
+msgstr "Wycofaj zmiany w dokumencie"
#: templates/wiki/tabs/history_view.html:14
msgid "View version"
-msgstr "Zobacz wersję"
+msgstr "Pokaż wersję"
#: templates/wiki/tabs/history_view_item.html:3
msgid "History"
#: templates/wiki/tabs/search_view.html:12
msgid "Replace all"
-msgstr "Zamień wszystko"
+msgstr "Zamień wszystkie"
+#: templates/wiki/tabs/search_view.html:15
msgid "Options"
msgstr "Opcje"
-#: templates/wiki/tabs/search_view.html:15
+#: templates/wiki/tabs/search_view.html:17
msgid "Case sensitive"
-msgstr "Rozróżniaj wielkość liter"
+msgstr "Wielkość znaków istotna"
-#: templates/wiki/tabs/search_view.html:17
+#: templates/wiki/tabs/search_view.html:19
msgid "From cursor"
-msgstr "Zacznij od kursora"
+msgstr "Od kursora"
#: templates/wiki/tabs/search_view_item.html:3
msgid "Search and replace"
#: templates/wiki/tabs/summary_view.html:13
msgid "Refresh from working copy"
-msgstr "Odśwież z edytowanej wersji"
+msgstr "Odśwież z wersji roboczej"
#: templates/wiki/tabs/summary_view.html:17
msgid "Title"
#: templates/wiki/tabs/summary_view.html:21
msgid "Go to the book's page"
-msgstr "Przejdź do strony książki"
+msgstr "Idź do strony książki"
#: templates/wiki/tabs/summary_view.html:24
msgid "Document ID"
#: templates/wiki/tabs/summary_view.html:41
msgid "pages"
-msgstr "stron maszynopisu"
+msgstr "stron"
#: templates/wiki/tabs/summary_view.html:41
msgid "untagged"
-msgstr "nieotagowane"
+msgstr "nie oznaczone"
#: templates/wiki/tabs/summary_view_item.html:3
msgid "Summary"
msgid "Visual editor"
msgstr "Edytor wizualny"
-#: templates/wiki/bootstrap.html:95
-msgid "Informations about lesson"
-msgstr "Informacje o lekcji"
-
-#: templates/wiki/bootstrap.html:97
-msgid "Stage"
-msgstr "Etap"
-
-#: templates/wiki/bootstrap.html:98
-msgid "Assignment"
-msgstr "Przypisano"
-
-#~ msgid "ZIP file"
-#~ msgstr "Plik ZIP"
-
-#~ msgid "Chunk with this slug already exists"
-#~ msgstr "Część z tym slugiem już istnieje"
-
-#~ msgid "Append to"
-#~ msgstr "Dołącz do"
-
-#~ msgid "title"
-#~ msgstr "tytuł"
-
-#~ msgid "scan gallery name"
-#~ msgstr "nazwa galerii skanów"
-
-#~ msgid "parent"
-#~ msgstr "rodzic"
-
-#~ msgid "parent number"
-#~ msgstr "numeracja rodzica"
-
-#~ msgid "book"
-#~ msgstr "książka"
-
-#~ msgid "books"
-#~ msgstr "książki"
-
-#~ msgid "Slug already used for %s"
-#~ msgstr "Slug taki sam jak dla pliku %s"
-
-#~ msgid "Slug already used in repository."
-#~ msgstr "Dokument o tym slugu już istnieje w repozytorium."
-
-#~ msgid "File should be UTF-8 encoded."
-#~ msgstr "Plik powinien mieć kodowanie UTF-8."
-
-#~ msgid "Tag added"
-#~ msgstr "Dodano tag"
-
-#~ msgid "Append book"
-#~ msgstr "Dołącz książkę"
-
-#~ msgid "edit"
-#~ msgstr "edytuj"
-
-#~ msgid "add basic document structure"
-#~ msgstr "dodaj podstawową strukturę dokumentu"
-
-#~ msgid "change master tag to"
-#~ msgstr "zmień tak master na"
-
-#~ msgid "add begin trimming tag"
-#~ msgstr "dodaj początkowy ogranicznik"
-
-#~ msgid "add end trimming tag"
-#~ msgstr "dodaj końcowy ogranicznik"
-
-#~ msgid "unstructured text"
-#~ msgstr "tekst bez struktury"
-
-#~ msgid "unknown XML"
-#~ msgstr "nieznany XML"
-
-#~ msgid "broken document"
-#~ msgstr "uszkodzony dokument"
-
-#~ msgid "Apply fixes"
-#~ msgstr "Wykonaj zmiany"
-
-#~ msgid "Append to other book"
-#~ msgstr "Dołącz do innej książki"
-
-#~ msgid "Last published"
-#~ msgstr "Ostatnio opublikowano"
-
-#~ msgid "Full XML"
-#~ msgstr "Pełny XML"
-
-#~ msgid "HTML version"
-#~ msgstr "Wersja HTML"
-
-#~ msgid "TXT version"
-#~ msgstr "Wersja TXT"
-
-#~ msgid "EPUB version"
-#~ msgstr "Wersja EPUB"
-
-#~ msgid "PDF version"
-#~ msgstr "Wersja PDF"
-
-#~ msgid "This book cannot be published yet"
-#~ msgstr "Ta książka nie może jeszcze zostać opublikowana"
-
-#~ msgid "Add chunk"
-#~ msgstr "Dodaj część"
-
-#~ msgid "Clear filter"
-#~ msgstr "Wyczyść filtr"
-
-#~ msgid "No books found."
-#~ msgstr "Nie znaleziono książek."
-
-#~ msgid "Your last edited documents"
-#~ msgstr "Twoje ostatnie edycje"
-
-#~ msgid "Bulk documents upload"
-#~ msgstr "Hurtowe dodawanie dokumentów"
-
-#~ msgid ""
-#~ "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with "
-#~ "<code>.xml</code> will be ignored."
-#~ msgstr ""
-#~ "Proszę wskazać archiwum ZIP z plikami XML w kodowaniu UTF-8. Pliki nie "
-#~ "kończące się na <code>.xml</code> zostaną zignorowane."
-
-#~ msgid "Upload"
-#~ msgstr "Załaduj"
-
-#~ msgid ""
-#~ "There have been some errors. No files have been added to the repository."
-#~ msgstr "Wystąpiły błędy. Żadne pliki nie zostały dodane do repozytorium."
-
-#~ msgid "Offending files"
-#~ msgstr "Błędne pliki"
-
-#~ msgid "Correct files"
-#~ msgstr "Poprawne pliki"
-
-#~ msgid "Files have been successfully uploaded to the repository."
-#~ msgstr "Pliki zostały dodane do repozytorium."
-
-#~ msgid "Uploaded files"
-#~ msgstr "Dodane pliki"
-
-#~ msgid "Skipped files"
-#~ msgstr "Pominięte pliki"
-
-#~ msgid "Files skipped due to no <code>.xml</code> extension"
-#~ msgstr "Pliki pominięte z powodu braku rozszerzenia <code>.xml</code>."
-
-#~ msgid "Users"
-#~ msgstr "Użytkownicy"
-
-#~ msgid "Unassigned"
-#~ msgstr "Nie przypisane"
-
-#~ msgid "All"
-#~ msgstr "Wszystkie"
-
-#~ msgid "Add"
-#~ msgstr "Dodaj"
-
-#~ msgid "Admin"
-#~ msgstr "Administracja"
-
-#~ msgid "First correction"
-#~ msgstr "Autokorekta"
-
-#~ msgid "Tagging"
-#~ msgstr "Tagowanie"
-
-#~ msgid "Initial Proofreading"
-#~ msgstr "Korekta"
-
-#~ msgid "Annotation Proofreading"
-#~ msgstr "Sprawdzenie przypisów źródła"
-
-#~ msgid "Modernisation"
-#~ msgstr "Uwspółcześnienie"
-
-#~ msgid "Themes"
-#~ msgstr "Motywy"
-
-#~ msgid "Editor's Proofreading"
-#~ msgstr "Ostateczna redakcja literacka"
-
-#~ msgid "Technical Editor's Proofreading"
-#~ msgstr "Ostateczna redakcja techniczna"
-
-#~ msgid "Finished stage: %s"
-#~ msgstr "Ukończony etap: %s"
-
-#~ msgid "Refresh"
-#~ msgstr "Odśwież"
-
-#~ msgid "Insert special character"
-#~ msgstr "Wstaw znak specjalny"
+#: views.py:47
+msgid "Published"
+msgstr "Opublikowane"
+++ /dev/null
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-03-21 14:43+0100\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
-"|| n%100>=20) ? 1 : 2);\n"
-
-#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:2759
-#: static/wiki/editor/src/editor/modules/data/data.js:13
-msgid "This is an empty document."
-msgstr "To jest pusty dokument."
-
-#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:2773
-#: static/wiki/editor/src/editor/modules/data/data.js:27
-msgid "The content of this document seems to be invalid - only XML source editing will be possible. :("
-msgstr "Wygląda na to, że dokument zawiera błędy - możliwa będzie jedynie edycja źródła. :("
-
-#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:2838
-#: static/wiki/editor/src/editor/modules/data/data.js:92
-msgid "Save Document"
-msgstr "Zapisz dokument"
-
-#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:2839
-#: static/wiki/editor/src/editor/modules/data/data.js:93
-msgid "Save"
-msgstr "Zapisz"
-
-#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:2894
-#: static/wiki/editor/src/editor/modules/data/data.js:148
-msgid "Restore Version"
-msgstr "Przywracanie wersji"
-
-#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:2895
-#: static/wiki/editor/src/editor/modules/data/data.js:149
-msgid "Restore"
-msgstr "Przywróć"
-
-#: static/wiki/editor/src/editor/modules/data/document.js:28
-msgid "Error"
-msgstr "Błąd"
-#: static/wiki/editor/src/editor/modules/documentCanvas/commands.js:141
-msgid "Create link"
-msgstr "Utwórz link"
-
-#: static/wiki/editor/src/editor/modules/documentCanvas/commands.js:142
-#: static/wiki/editor/src/editor/modules/documentCanvas/commands.js:224
-msgid "Apply"
-msgstr "Zatwierdź"
-
-#: static/wiki/editor/src/editor/modules/data/document.js:29
-msgid "Something wrong happend when applying this change so it was undone."
-msgstr "Coś poszło nie tak podczas wprowadzania tej zmiany, więc została ona cofnięta."
-#: static/wiki/editor/src/editor/modules/documentCanvas/commands.js:145
-#: static/wiki/editor/src/editor/modules/documentCanvas/commands.js:227
-msgid "Link"
-msgstr "Link"
-
-#: static/wiki/editor/src/editor/modules/documentCanvas/commands.js:223
-msgid "Edit link"
-msgstr "Edytuj link"
-
-#: static/wiki/editor/src/editor/modules/mainBar/template.html:5
-msgid "Exit"
-msgstr "Wyjdź"
-
-#: static/wiki/editor/src/editor/modules/nodePane/template.html:3
-msgid "Current node"
-msgstr "Bieżący węzeł"
-#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:3172
-#: static/wiki/editor/src/editor/modules/rng/rng.js:73
-msgid "Editor"
-msgstr "Edytor"
-
-#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:3173
-#: static/wiki/editor/build/rng.js:3188
-#: static/wiki/editor/src/editor/modules/rng/rng.js:74
-#: static/wiki/editor/src/editor/modules/rng/rng.js:89
-msgid "Source"
-msgstr "Źródło"
-
-#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:3174
-#: static/wiki/editor/src/editor/modules/rng/rng.js:75
-msgid "History"
-msgstr "Historia"
-
-#: static/wiki/editor/src/editor/modules/documentCanvas/canvas/keyboard.js:61
-#: static/wiki/editor/src/editor/modules/documentCanvas/canvas/keyboard.js:86
-#: static/wiki/editor/src/editor/modules/documentCanvas/canvas/keyboard.js:124
-msgid "Splitting text"
-msgstr "Rozbicie tekstu"
-#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:3212
-#: static/wiki/editor/src/editor/modules/rng/rng.js:113
-msgid "Saving..."
-msgstr "Zapisywanie..."
-
-#: static/wiki/editor/src/editor/modules/documentCanvas/canvas/keyboard.js:309
-msgid "Remove text"
-msgstr "Usuń tekst"
-#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:3223
-#: static/wiki/editor/src/editor/modules/rng/rng.js:124
-msgid "Restoring version "
-msgstr "Przywracanie wersji"
-
-#: static/wiki/editor/src/editor/modules/documentToolbar/actionView.js:49
-#: static/wiki/editor/src/editor/modules/statusBar/statusBar.js:28
-msgid "error :("
-msgstr "błąd :("
-#: static/wiki/build/rng.js:6 static/wiki/editor/build/rng.js:3353
-#: static/wiki/editor/src/editor/modules/rng/rng.js:247
-msgid "editor"
-msgstr "edytor"
-
-#: static/wiki/editor/build/rng.js:3364
-#: static/wiki/editor/src/editor/modules/rng/rng.js:273
-msgid "Do you really want to exit?"
-msgstr "Czy na pewno chcesz zakończyć pracę?"
-
-#: static/wiki/editor/build/rng.js:3366
-#: static/wiki/editor/src/editor/modules/rng/rng.js:275
-msgid "Document contains unsaved changes!"
-msgstr "Dokument zawiera niezapisane zmiany"
-
-#: static/wiki/editor/src/editor/modules/data/data.js:137
-msgid "Local draft of a document exists"
-msgstr "Istnieje kopia lokalna dokumentu"
-
-#: static/wiki/editor/src/editor/modules/data/data.js:138
-msgid ""
-"Unsaved local draft of this version of the document exists in your browser. "
-"Do you want to load it instead?"
-msgstr ""
-"Twoja przeglądarka posiada niezapisaną jeszcze na serwerze lokalną kopię tej "
-"wersji dokumentu. Czy chcesz jej teraz użyć?"
-
-#: static/wiki/editor/src/editor/modules/data/data.js:139
-msgid "Yes, restore local draft"
-msgstr "Tak, chcę użyć lokalną kopię"
-
-#: static/wiki/editor/src/editor/modules/data/data.js:140
-msgid "No, use version loaded from the server"
-msgstr "Nie, chcę załadować wersję z serwera"
-
-#: static/wiki/editor/src/editor/modules/data/dialog.js:30
-msgid "Submit"
-msgstr "Zatwierdź"
-
-#: static/wiki/editor/src/editor/modules/data/dialog.js:31
-msgid "Cancel"
-msgstr "Anuluj"
-
-#: static/wiki/editor/src/editor/modules/mainBar/mainBar.js:15
-msgid "anonymous"
-msgstr "anonim"
-
-#: static/wiki/editor/src/editor/modules/rng/rng.js:119
-msgid "Saving document"
-msgstr "Zapisywanie dokumentu"
-
-#: static/wiki/editor/src/editor/modules/rng/rng.js:120
-msgid "Saving local copy"
-msgstr "Zapisywanie kopii lokalnej"
-
-#: static/wiki/editor/src/editor/modules/rng/rng.js:128
-msgid "Document saved"
-msgstr "Dokument zapisany"
-
-#: static/wiki/editor/src/editor/modules/rng/rng.js:129
-msgid "Local copy saved"
-msgstr "Wersja robocza zapisana"
-
-msgid "Draft Saved"
-msgstr "Zapisana wersja robocza"
-
-msgid "no draft exists"
-msgstr "brak wersji roboczej"
-
-msgid "drop a working draft"
-msgstr "porzuć wersję roboczą"
-
-#: static/wiki/editor/src/editor/modules/documentCanvas/canvas/canvas.js:45
-msgid "Changing text"
-msgstr "Edycja tekstu"
-
-#: static/wiki/editor/src/editor/modules/metadataEditor/metadataEditor.js:26
-msgid "Add metadata row"
-msgstr "Dodaj wiersz metadanych"
-
-#: static/wiki/editor/src/editor/modules/metadataEditor/metadataEditor.js:32
-msgid "Remove metadata row"
-msgstr "Usuń wiersz metadanych"
-
-#: static/wiki/editor/src/editor/modules/statusBar/statusBar.js:33
-msgid "Undescribed action"
-msgstr "Działanie nieopisane"
-#: static/wiki/editor/src/editor/modules/metadataEditor/metadataEditor.js:59
-msgid "Metadata edit"
-msgstr "Edycja metadanych"
-
-#: static/wiki/editor/src/editor/modules/statusBar/statusBar.js:22
-msgid "Action not allowed"
-msgstr "Działanie niedozwolone"
-
-#: static/wiki/editor/src/editor/plugins/core/core.js:63
-msgid "Undo"
-msgstr "Cofnij"
-
-#: static/wiki/editor/src/editor/plugins/core/core.js:63
-msgid "Redo"
-msgstr "Powtórz"
-
-#: static/wiki/editor/src/editor/plugins/core/core.js:64
-msgid "There is nothing to undo"
-msgstr "Nie ma nic więcej do cofnięcia"
-
-#: static/wiki/editor/src/editor/plugins/core/core.js:64
-msgid "There is nothing to redo"
-msgstr "Nie ma nic więcej do powtórzenia"
-
-#: static/wiki/editor/src/editor/plugins/core/core.js:66
-msgid "unknown operation"
-msgstr "Nieznana operacja"
-
-#: static/wiki/editor/src/editor/plugins/core/core.js:126
-msgid "Insert comment"
-msgstr "Wstaw komentarz"
-
-#: static/wiki/editor/src/editor/plugins/core/core.js:148
-msgid "Hide grid"
-msgstr "Schowaj siatkę"
-
-#: static/wiki/editor/src/editor/plugins/core/core.js:148
-msgid "Show grid"
-msgstr "Pokaż siatkę"
-#: static/wiki/editor/src/editor/plugins/core/core.js:293
-msgid "link"
-msgstr "link"
-
-#: static/wiki/editor/src/editor/plugins/core/footnote.js:47
-#: static/wiki/editor/src/editor/plugins/core/core.js:306
-msgid "Create link from selection"
-msgstr "Utwórz link z zaznaczonego tekstu"
-
-#: static/wiki/editor/src/editor/plugins/core/core.js:325
-msgid "Mark as emphasized"
-msgstr "Oznacz jako wyróżnione"
-
-#: static/wiki/editor/src/editor/plugins/core/core.js:365
-msgid "Remove emphasis"
-msgstr "Usuń wyróżnienie"
-
-#: static/wiki/editor/src/editor/plugins/core/core.js:366
-#: static/wiki/editor/src/editor/plugins/core/core.js:326
-msgid "Mark as citation"
-msgstr "Oznacz jako cytat"
-
-#: static/wiki/editor/src/editor/plugins/core/core.js:366
-msgid "Remove citation"
-msgstr "Usuń cytat"
-
-msgid "Create footnote from selection"
-msgstr "Utwórz przypis z zaznaczonego tekstu"
-
-#: static/wiki/editor/src/editor/plugins/core/footnote.js:54
-msgid "Insert footnote after cursor"
-msgstr "Wstaw przypis za kursorem"
-
-#: static/wiki/editor/src/editor/plugins/core/footnote.js:62
-msgid "Cannot insert footnote after root node"
-msgstr "Nie można wstawić przypisu za węzłem głównym"
-
-#: static/wiki/editor/src/editor/plugins/core/footnote.js:67
-msgid "Insert footnote after node"
-msgstr "Wstaw przypis za węzłem"
-
-#: static/wiki/editor/src/editor/plugins/core/lists.js:112
-msgid "bull. list"
-msgstr "lista pkt."
-#: static/wiki/editor/src/editor/plugins/core/insert.js:60
-msgid "Missing tag name"
-msgstr "Nie wybrano tagu"
-
-#: static/wiki/editor/src/editor/plugins/core/lists.js:112
-msgid "num. list"
-msgstr "lista num."
-#: static/wiki/editor/src/editor/plugins/core/insert.js:65
-#, c-format
-msgid "Wrap text with %s"
-msgstr "Obejmij tekst przez %s"
-
-#: static/wiki/editor/src/editor/plugins/core/insert.js:71
-#, c-format
-msgid "Wrap nodes with %s"
-msgstr "Obejmij węzły przez %s"
-
-#: static/wiki/editor/src/editor/plugins/core/insert.js:78
-#, c-format
-msgid "Wrap current node with %s"
-msgstr "Obejmij bieżący węzeł przez %s"
-
-#: static/wiki/editor/src/editor/plugins/core/insert.js:85
-msgid "Cannot insert after root node"
-msgstr "Nie można wstawić węzła za węzłem głównym"
-
-#: static/wiki/editor/src/editor/plugins/core/insert.js:90
-#, c-format
-msgid "Insert %s after current"
-msgstr "Wstaw %s za bieżący węzeł"
-
-#: static/wiki/editor/src/editor/plugins/core/lists.js:91
-#, c-format
-msgid "Change list type to %s"
-msgstr "Zmień typ listy na %s"
-
-#: static/wiki/editor/src/editor/plugins/core/lists.js:98
-msgid "Remove list"
-msgstr "Usuń listę"
-
-#: static/wiki/editor/src/editor/plugins/core/lists.js:107
-#, c-format
-msgid "Make %s fragment(s) into list"
-msgstr "Stwórz listę z %s fragmentów"
-
-#: static/wiki/editor/src/editor/plugins/core/switch.js:68
-msgid "header"
-msgstr "nagłówek"
-#: static/wiki/editor/src/editor/plugins/core/lists.js:122
-#: static/wiki/editor/src/editor/plugins/core/lists.js:123
-msgid "list item up"
-msgstr "Element listy do góry"
-
-#: static/wiki/editor/src/editor/plugins/core/remove.js:21
-msgid "Unwrap with siblings"
-msgstr "Odpakuj wraz sąsiadami"
-
-#: static/wiki/editor/src/editor/plugins/core/remove.js:21
-msgid "Cannot unwrap children of a root node"
-msgstr "Nie można odpakować dzieci głównego węzła"
-
-#: static/wiki/editor/src/editor/plugins/core/remove.js:49
-msgid "Cannot remove root node"
-msgstr "Nie można usunąć głównego węzła"
-
-#: static/wiki/editor/src/editor/plugins/core/switch.js:69
-msgid "paragraf"
-msgstr "akapit"
-#: static/wiki/editor/src/editor/plugins/core/remove.js:54
-msgid "Remove node"
-msgstr "Usuń węzeł"
-
-#: static/wiki/editor/src/editor/plugins/core/templates.js:27
-msgid "No template selected"
-msgstr "Nie wybrano template'u"
-
-#: static/wiki/editor/src/editor/plugins/core/templates.js:32
-msgid "Wrong node selected"
-msgstr "Wybrany niepoprawny węzeł"
-
-#: static/wiki/editor/src/editor/plugins/core/templates.js:37
-#, c-format
-msgid "Insert template %s"
-msgstr "Wstaw template %s"
-
-msgid "Switch to"
-msgstr "Zamień na"
-
-msgid "change"
-msgstr "zmień"
-
-msgid "remove"
-msgstr "usuń"
-
-msgid "Remove link"
-msgstr "Usuń link"
-
-msgid "edit"
-msgstr "edytuj"
-
-msgid "Delete"
-msgstr "Usuń"
-
-msgid "Respond"
-msgstr "Odpowiedz"
-
-msgid "Comment"
-msgstr "Skomentuj"
-
-msgid "Delete this comment?"
-msgstr "Usunąć ten komentarz?"
-
-msgid "Metadata"
-msgstr "Metadane"
-
-msgid "Document Metadata"
-msgstr "Metadane dokumentu"
-
-msgid "Close"
-msgstr "Zamknij"
-
-msgid "Exercise"
-msgstr "Zadanie"
-
-msgid "Insert exercise"
-msgstr "Wstaw zadanie"
-
-msgid "Single Choice"
-msgstr "Wybór jednokrotny"
-
-msgid "Multiple Choice"
-msgstr "Wybór wielokrotny"
-
-msgid "Gaps"
-msgstr "Luki"
-
-msgid "Replace"
-msgstr "Zastąp"
-
-msgid "Order"
-msgstr "Uporządkuj"
-
-msgid "True or False"
-msgstr "Prawda lub fałsz"
-
-msgid "True"
-msgstr "Prawda"
-
-msgid "False"
-msgstr "Fałsz"
-
-msgid "Mark to replace"
-msgstr "Oznacz do zastąpienia"
-
-msgid "Edit replace mark"
-msgstr "Edytuj zastąpienie"
-
-msgid "Remove replace mark"
-msgstr "Usuń zastąpienie"
-
-msgid "Create a gap"
-msgstr "Stwórz lukę"
-
-msgid "Remove a gap"
-msgstr "Usuń lukę"
-
-msgid "Description goes here"
-msgstr "Tu wpisz polecenie"
-
-msgid "Removing exercise"
-msgstr "Usuwanie zadania"
-
-msgid "Do you really want to remove this exercise?"
-msgstr "Czy na pewno chcesz usunąć to zadanie?"
-
-msgid "Yes"
-msgstr "Tak"
-
-msgid "No, don't do anything!"
-msgstr "Nie, nic nie rób!"
-
-msgid "Edit text to replace with"
-msgstr "Edytuj tekst zastępujący"
-
-msgid "Initial"
-msgstr "Inicjalnie"
-
-msgid "Solution"
-msgstr "Rozwiązanie"
-
-msgid "Change solution"
-msgstr "Zmień rozwiązanie"
-
-msgid "First item"
-msgstr "Pierwszy element"
\ No newline at end of file
+++ /dev/null
-# encoding: 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):
- pass
-
-
- def backwards(self, orm):
- pass
-
-
- models = {
-
- }
-
- complete_apps = ['wiki']
+++ /dev/null
-# encoding: 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 'Theme'
- db.create_table('wiki_theme', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=50)),
- ))
- db.send_create_signal('wiki', ['Theme'])
-
- if not db.dry_run:
- from django.core.management import call_command
- call_command("loaddata", "initial_themes.yaml")
-
-
-
- def backwards(self, orm):
-
- # Deleting model 'Theme'
- db.delete_table('wiki_theme')
-
-
- models = {
- 'wiki.theme': {
- 'Meta': {'object_name': 'Theme'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'})
- }
- }
-
- complete_apps = ['wiki']
-# -*- coding: utf-8 -*-
-#
-# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from django.db import models
-from django.utils.translation import ugettext_lazy as _
-
-import logging
-logger = logging.getLogger("fnp.wiki")
-
-
-class Theme(models.Model):
- name = models.CharField(_('name'), max_length=50, unique=True)
-
- class Meta:
- ordering = ('name',)
- verbose_name = _('theme')
- verbose_name_plural = _('themes')
-
- def __unicode__(self):
- return self.name
-
- def __repr__(self):
- return "Theme(name=%r)" % self.name
-
--- /dev/null
+/* record log messages for testing */
+// var logAllIds = function() {
+// var allTags = document.head.getElementsByTagName('style');
+// var ids = [];
+// for (var tg = 0; tg < allTags.length; tg++) {
+// var tag = allTags[tg];
+// if (tag.id) {
+// console.log(tag.id);
+// }
+// }
+// };
+
+var logMessages = [],
+ realConsoleLog = console.log;
+console.log = function(msg) {
+ logMessages.push(msg);
+ realConsoleLog.call(console, msg);
+};
+
+var testLessEqualsInDocument = function() {
+ testLessInDocument(testSheet);
+};
+
+var testLessErrorsInDocument = function(isConsole) {
+ testLessInDocument(isConsole ? testErrorSheetConsole : testErrorSheet);
+};
+
+var testLessInDocument = function(testFunc) {
+ var links = document.getElementsByTagName('link'),
+ typePattern = /^text\/(x-)?less$/;
+
+ for (var i = 0; i < links.length; i++) {
+ if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
+ (links[i].type.match(typePattern)))) {
+ testFunc(links[i]);
+ }
+ }
+};
+
+var testSheet = function(sheet) {
+ it(sheet.id + " should match the expected output", function() {
+ var lessOutputId = sheet.id.replace("original-", ""),
+ expectedOutputId = "expected-" + lessOutputId,
+ lessOutputObj,
+ lessOutput,
+ expectedOutputHref = document.getElementById(expectedOutputId).href,
+ expectedOutput = loadFile(expectedOutputHref);
+
+ // Browser spec generates less on the fly, so we need to loose control
+ waitsFor(function() {
+ lessOutputObj = document.getElementById(lessOutputId);
+ // the type condition is necessary because of inline browser tests
+ return lessOutputObj !== null && lessOutputObj.type === "text/css";
+ }, "generation of " + lessOutputId + "", 700);
+
+ runs(function() {
+ lessOutput = lessOutputObj.innerText;
+ });
+
+ waitsFor(function() {
+ return expectedOutput.loaded;
+ }, "failed to load expected outout", 10000);
+
+ runs(function() {
+ // use sheet to do testing
+ expect(expectedOutput.text).toEqual(lessOutput);
+ });
+ });
+};
+
+//TODO: do it cleaner - the same way as in css
+
+function extractId(href) {
+ return href.replace(/^[a-z-]+:\/+?[^\/]+/, '') // Remove protocol & domain
+ .replace(/^\//, '') // Remove root /
+ .replace(/\.[a-zA-Z]+$/, '') // Remove simple extension
+ .replace(/[^\.\w-]+/g, '-') // Replace illegal characters
+ .replace(/\./g, ':'); // Replace dots with colons(for valid id)
+}
+
+var testErrorSheet = function(sheet) {
+ it(sheet.id + " should match an error", function() {
+ var lessHref = sheet.href,
+ id = "less-error-message:" + extractId(lessHref),
+ // id = sheet.id.replace(/^original-less:/, "less-error-message:"),
+ errorHref = lessHref.replace(/.less$/, ".txt"),
+ errorFile = loadFile(errorHref),
+ actualErrorElement,
+ actualErrorMsg;
+
+ // Less.js sets 10ms timer in order to add error message on top of page.
+ waitsFor(function() {
+ actualErrorElement = document.getElementById(id);
+ return actualErrorElement !== null;
+ }, "error message was not generated", 70);
+
+ runs(function() {
+ actualErrorMsg = actualErrorElement.innerText
+ .replace(/\n\d+/g, function(lineNo) {
+ return lineNo + " ";
+ })
+ .replace(/\n\s*in /g, " in ")
+ .replace("\n\n", "\n");
+ });
+
+ waitsFor(function() {
+ return errorFile.loaded;
+ }, "failed to load expected outout", 10000);
+
+ runs(function() {
+ var errorTxt = errorFile.text
+ .replace("{path}", "")
+ .replace("{pathrel}", "")
+ .replace("{pathhref}", "http://localhost:8081/test/less/errors/")
+ .replace("{404status}", " (404)");
+ expect(errorTxt).toEqual(actualErrorMsg);
+ if (errorTxt == actualErrorMsg) {
+ actualErrorElement.style.display = "none";
+ }
+ });
+ });
+};
+
+var testErrorSheetConsole = function(sheet) {
+ it(sheet.id + " should match an error", function() {
+ var lessHref = sheet.href,
+ id = sheet.id.replace(/^original-less:/, "less-error-message:"),
+ errorHref = lessHref.replace(/.less$/, ".txt"),
+ errorFile = loadFile(errorHref),
+ actualErrorElement = document.getElementById(id),
+ actualErrorMsg = logMessages[logMessages.length - 1];
+
+ describe("the error", function() {
+ expect(actualErrorElement).toBe(null);
+
+ });
+
+ /*actualErrorMsg = actualErrorElement.innerText
+ .replace(/\n\d+/g, function(lineNo) { return lineNo + " "; })
+ .replace(/\n\s*in /g, " in ")
+ .replace("\n\n", "\n");*/
+
+ waitsFor(function() {
+ return errorFile.loaded;
+ }, "failed to load expected outout", 10000);
+
+ runs(function() {
+ var errorTxt = errorFile.text
+ .replace("{path}", "")
+ .replace("{pathrel}", "")
+ .replace("{pathhref}", "http://localhost:8081/browser/less/")
+ .replace("{404status}", " (404)")
+ .trim();
+ expect(errorTxt).toEqual(actualErrorMsg);
+ });
+ });
+};
+
+var loadFile = function(href) {
+ var request = new XMLHttpRequest(),
+ response = {
+ loaded: false,
+ text: ""
+ };
+ request.open('GET', href, true);
+ request.onload = function(e) {
+ response.text = request.response.replace(/\r/g, "");
+ response.loaded = true;
+ };
+ request.send();
+ return response;
+};
+
+(function() {
+ var jasmineEnv = jasmine.getEnv();
+ jasmineEnv.updateInterval = 1000;
+
+ var htmlReporter = new jasmine.HtmlReporter();
+
+ jasmineEnv.addReporter(htmlReporter);
+
+ jasmineEnv.specFilter = function(spec) {
+ return htmlReporter.specFilter(spec);
+ };
+
+ var currentWindowOnload = window.onload;
+
+ window.onload = function() {
+ if (currentWindowOnload) {
+ currentWindowOnload();
+ }
+ execJasmine();
+ };
+
+ function execJasmine() {
+ setTimeout(function() {
+ jasmineEnv.execute();
+ }, 3000);
+ }
+
+})();
\ No newline at end of file
--- /dev/null
+.test {
+ color: #ff0000;
+}
--- /dev/null
+.testisimported {
+ color: gainsboro;
+}
+.test {
+ color1: #008000;
+ color2: #800080;
+}
--- /dev/null
+@import "http://localhost:8081/test/browser/less/imports/modify-this.css";
+@import "http://localhost:8081/test/browser/less/imports/modify-again.css";
+.modify {
+ my-url: url("http://localhost:8081/test/browser/less/imports/a.png");
+}
+.modify {
+ my-url: url("http://localhost:8081/test/browser/less/imports/b.png");
+}
+@font-face {
+ src: local(Futura-Medium), url(http://localhost:8081/test/browser/less/relative-urls/fonts.svg#MyGeometricModern) format("svg");
+}
+#shorthands {
+ background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
+}
+#misc {
+ background-image: url(http://localhost:8081/test/browser/less/relative-urls/images/image.jpg);
+}
+#data-uri {
+ background: url(data:image/png;charset=utf-8;base64,
+ kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
+ k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
+ kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
+ background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
+ background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
+}
+#svg-data-uri {
+ background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
+}
+.comma-delimited {
+ background: url(http://localhost:8081/test/browser/less/relative-urls/bg.jpg) no-repeat, url(http://localhost:8081/test/browser/less/relative-urls/bg.png) repeat-x top left, url(http://localhost:8081/test/browser/less/relative-urls/bg);
+}
+.values {
+ url: url('http://localhost:8081/test/browser/less/relative-urls/Trebuchet');
+}
--- /dev/null
+@import "https://www.github.com/cloudhead/imports/modify-this.css";
+@import "https://www.github.com/cloudhead/imports/modify-again.css";
+.modify {
+ my-url: url("https://www.github.com/cloudhead/imports/a.png");
+}
+.modify {
+ my-url: url("https://www.github.com/cloudhead/imports/b.png");
+}
+@font-face {
+ src: local(Futura-Medium), url(https://www.github.com/cloudhead/less.js/fonts.svg#MyGeometricModern) format("svg");
+}
+#shorthands {
+ background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
+}
+#misc {
+ background-image: url(https://www.github.com/cloudhead/less.js/images/image.jpg);
+}
+#data-uri {
+ background: url(data:image/png;charset=utf-8;base64,
+ kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
+ k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
+ kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
+ background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
+ background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
+}
+#svg-data-uri {
+ background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
+}
+.comma-delimited {
+ background: url(https://www.github.com/cloudhead/less.js/bg.jpg) no-repeat, url(https://www.github.com/cloudhead/less.js/bg.png) repeat-x top left, url(https://www.github.com/cloudhead/less.js/bg);
+}
+.values {
+ url: url('https://www.github.com/cloudhead/less.js/Trebuchet');
+}
--- /dev/null
+@import "https://www.github.com/modify-this.css";
+@import "https://www.github.com/modify-again.css";
+.modify {
+ my-url: url("https://www.github.com/a.png");
+}
+.modify {
+ my-url: url("https://www.github.com/b.png");
+}
+@font-face {
+ src: local(Futura-Medium), url(https://www.github.com/fonts.svg#MyGeometricModern) format("svg");
+}
+#shorthands {
+ background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
+}
+#misc {
+ background-image: url(https://www.github.com/images/image.jpg);
+}
+#data-uri {
+ background: url(data:image/png;charset=utf-8;base64,
+ kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
+ k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
+ kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
+ background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
+ background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
+}
+#svg-data-uri {
+ background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
+}
+.comma-delimited {
+ background: url(https://www.github.com/bg.jpg) no-repeat, url(https://www.github.com/bg.png) repeat-x top left, url(https://www.github.com/bg);
+}
+.values {
+ url: url('https://www.github.com/Trebuchet');
+}
--- /dev/null
+@import "http://localhost:8081/test/browser/less/modify-this.css";
+@import "http://localhost:8081/test/browser/less/modify-again.css";
+.modify {
+ my-url: url("http://localhost:8081/test/browser/less/a.png");
+}
+.modify {
+ my-url: url("http://localhost:8081/test/browser/less/b.png");
+}
+@font-face {
+ src: local(Futura-Medium), url(http://localhost:8081/test/browser/less/fonts.svg#MyGeometricModern) format("svg");
+}
+#shorthands {
+ background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
+}
+#misc {
+ background-image: url(http://localhost:8081/test/browser/less/images/image.jpg);
+}
+#data-uri {
+ background: url(data:image/png;charset=utf-8;base64,
+ kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
+ k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
+ kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
+ background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
+ background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
+}
+#svg-data-uri {
+ background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
+}
+.comma-delimited {
+ background: url(http://localhost:8081/test/browser/less/bg.jpg) no-repeat, url(http://localhost:8081/test/browser/less/bg.png) repeat-x top left, url(http://localhost:8081/test/browser/less/bg);
+}
+.values {
+ url: url('http://localhost:8081/test/browser/less/Trebuchet');
+}
+#data-uri {
+ uri: url('http://localhost:8081/test/data/image.jpg');
+}
+#data-uri-guess {
+ uri: url('http://localhost:8081/test/data/image.jpg');
+}
+#data-uri-ascii {
+ uri-1: url('http://localhost:8081/test/data/page.html');
+ uri-2: url('http://localhost:8081/test/data/page.html');
+}
+#data-uri-toobig {
+ uri: url('http://localhost:8081/test/data/data-uri-fail.png');
+}
+#svg-functions {
+ background-image: url('data:image/svg+xml,<?xml version="1.0" ?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none"><linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="0%" stop-color="#000000"/><stop offset="100%" stop-color="#ffffff"/></linearGradient><rect x="0" y="0" width="1" height="1" fill="url(#gradient)" /></svg>');
+ background-image: url('data:image/svg+xml,<?xml version="1.0" ?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none"><linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="0%" stop-color="#000000"/><stop offset="3%" stop-color="#ffa500"/><stop offset="100%" stop-color="#ffffff"/></linearGradient><rect x="0" y="0" width="1" height="1" fill="url(#gradient)" /></svg>');
+ background-image: url('data:image/svg+xml,<?xml version="1.0" ?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none"><linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="1%" stop-color="#c4c4c4"/><stop offset="3%" stop-color="#ffa500"/><stop offset="5%" stop-color="#008000"/><stop offset="95%" stop-color="#ffffff"/></linearGradient><rect x="0" y="0" width="1" height="1" fill="url(#gradient)" /></svg>');
+}
--- /dev/null
+/*
+ PhantomJS does not implement bind. this is from
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
+ */
+if (!Function.prototype.bind) {
+ Function.prototype.bind = function (oThis) {
+ if (typeof this !== "function") {
+ // closest thing possible to the ECMAScript 5 internal IsCallable function
+ throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
+ }
+
+ var aArgs = Array.prototype.slice.call(arguments, 1),
+ fToBind = this,
+ fNOP = function () {},
+ fBound = function () {
+ return fToBind.apply(this instanceof fNOP && oThis
+ ? this
+ : oThis,
+ aArgs.concat(Array.prototype.slice.call(arguments)));
+ };
+
+ fNOP.prototype = this.prototype;
+ fBound.prototype = new fNOP();
+
+ return fBound;
+ };
+}
\ No newline at end of file
--- /dev/null
+jasmine.HtmlReporterHelpers = {};
+
+jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
+ var el = document.createElement(type);
+
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(document.createTextNode(child));
+ } else {
+ if (child) {
+ el.appendChild(child);
+ }
+ }
+ }
+
+ for (var attr in attrs) {
+ if (attr == "className") {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+};
+
+jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
+ var results = child.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.skipped) {
+ status = 'skipped';
+ }
+
+ return status;
+};
+
+jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
+ var parentDiv = this.dom.summary;
+ var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
+ var parent = child[parentSuite];
+
+ if (parent) {
+ if (typeof this.views.suites[parent.id] == 'undefined') {
+ this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
+ }
+ parentDiv = this.views.suites[parent.id].element;
+ }
+
+ parentDiv.appendChild(childElement);
+};
+
+
+jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
+ for(var fn in jasmine.HtmlReporterHelpers) {
+ ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
+ }
+};
+
+jasmine.HtmlReporter = function(_doc) {
+ var self = this;
+ var doc = _doc || window.document;
+
+ var reporterView;
+
+ var dom = {};
+
+ // Jasmine Reporter Public Interface
+ self.logRunningSpecs = false;
+
+ self.reportRunnerStarting = function(runner) {
+ var specs = runner.specs() || [];
+
+ if (specs.length == 0) {
+ return;
+ }
+
+ createReporterDom(runner.env.versionString());
+ doc.body.appendChild(dom.reporter);
+ setExceptionHandling();
+
+ reporterView = new jasmine.HtmlReporter.ReporterView(dom);
+ reporterView.addSpecs(specs, self.specFilter);
+ };
+
+ self.reportRunnerResults = function(runner) {
+ reporterView && reporterView.complete();
+ };
+
+ self.reportSuiteResults = function(suite) {
+ reporterView.suiteComplete(suite);
+ };
+
+ self.reportSpecStarting = function(spec) {
+ if (self.logRunningSpecs) {
+ self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+ }
+ };
+
+ self.reportSpecResults = function(spec) {
+ reporterView.specComplete(spec);
+ };
+
+ self.log = function() {
+ var console = jasmine.getGlobal().console;
+ if (console && console.log) {
+ if (console.log.apply) {
+ console.log.apply(console, arguments);
+ } else {
+ console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+ }
+ }
+ };
+
+ self.specFilter = function(spec) {
+ if (!focusedSpecName()) {
+ return true;
+ }
+
+ return spec.getFullName().indexOf(focusedSpecName()) === 0;
+ };
+
+ return self;
+
+ function focusedSpecName() {
+ var specName;
+
+ (function memoizeFocusedSpec() {
+ if (specName) {
+ return;
+ }
+
+ var paramMap = [];
+ var params = jasmine.HtmlReporter.parameters(doc);
+
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+ }
+
+ specName = paramMap.spec;
+ })();
+
+ return specName;
+ }
+
+ function createReporterDom(version) {
+ dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
+ dom.banner = self.createDom('div', { className: 'banner' },
+ self.createDom('span', { className: 'title' }, "Jasmine "),
+ self.createDom('span', { className: 'version' }, version)),
+
+ dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
+ dom.alert = self.createDom('div', {className: 'alert'},
+ self.createDom('span', { className: 'exceptions' },
+ self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'),
+ self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))),
+ dom.results = self.createDom('div', {className: 'results'},
+ dom.summary = self.createDom('div', { className: 'summary' }),
+ dom.details = self.createDom('div', { id: 'details' }))
+ );
+ }
+
+ function noTryCatch() {
+ return window.location.search.match(/catch=false/);
+ }
+
+ function searchWithCatch() {
+ var params = jasmine.HtmlReporter.parameters(window.document);
+ var removed = false;
+ var i = 0;
+
+ while (!removed && i < params.length) {
+ if (params[i].match(/catch=/)) {
+ params.splice(i, 1);
+ removed = true;
+ }
+ i++;
+ }
+ if (jasmine.CATCH_EXCEPTIONS) {
+ params.push("catch=false");
+ }
+
+ return params.join("&");
+ }
+
+ function setExceptionHandling() {
+ var chxCatch = document.getElementById('no_try_catch');
+
+ if (noTryCatch()) {
+ chxCatch.setAttribute('checked', true);
+ jasmine.CATCH_EXCEPTIONS = false;
+ }
+ chxCatch.onclick = function() {
+ window.location.search = searchWithCatch();
+ };
+ }
+};
+jasmine.HtmlReporter.parameters = function(doc) {
+ var paramStr = doc.location.search.substring(1);
+ var params = [];
+
+ if (paramStr.length > 0) {
+ params = paramStr.split('&');
+ }
+ return params;
+}
+jasmine.HtmlReporter.sectionLink = function(sectionName) {
+ var link = '?';
+ var params = [];
+
+ if (sectionName) {
+ params.push('spec=' + encodeURIComponent(sectionName));
+ }
+ if (!jasmine.CATCH_EXCEPTIONS) {
+ params.push("catch=false");
+ }
+ if (params.length > 0) {
+ link += params.join("&");
+ }
+
+ return link;
+};
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
+jasmine.HtmlReporter.ReporterView = function(dom) {
+ this.startedAt = new Date();
+ this.runningSpecCount = 0;
+ this.completeSpecCount = 0;
+ this.passedCount = 0;
+ this.failedCount = 0;
+ this.skippedCount = 0;
+
+ this.createResultsMenu = function() {
+ this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
+ this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
+ ' | ',
+ this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
+
+ this.summaryMenuItem.onclick = function() {
+ dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
+ };
+
+ this.detailsMenuItem.onclick = function() {
+ showDetails();
+ };
+ };
+
+ this.addSpecs = function(specs, specFilter) {
+ this.totalSpecCount = specs.length;
+
+ this.views = {
+ specs: {},
+ suites: {}
+ };
+
+ for (var i = 0; i < specs.length; i++) {
+ var spec = specs[i];
+ this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
+ if (specFilter(spec)) {
+ this.runningSpecCount++;
+ }
+ }
+ };
+
+ this.specComplete = function(spec) {
+ this.completeSpecCount++;
+
+ if (isUndefined(this.views.specs[spec.id])) {
+ this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
+ }
+
+ var specView = this.views.specs[spec.id];
+
+ switch (specView.status()) {
+ case 'passed':
+ this.passedCount++;
+ break;
+
+ case 'failed':
+ this.failedCount++;
+ break;
+
+ case 'skipped':
+ this.skippedCount++;
+ break;
+ }
+
+ specView.refresh();
+ this.refresh();
+ };
+
+ this.suiteComplete = function(suite) {
+ var suiteView = this.views.suites[suite.id];
+ if (isUndefined(suiteView)) {
+ return;
+ }
+ suiteView.refresh();
+ };
+
+ this.refresh = function() {
+
+ if (isUndefined(this.resultsMenu)) {
+ this.createResultsMenu();
+ }
+
+ // currently running UI
+ if (isUndefined(this.runningAlert)) {
+ this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" });
+ dom.alert.appendChild(this.runningAlert);
+ }
+ this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
+
+ // skipped specs UI
+ if (isUndefined(this.skippedAlert)) {
+ this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" });
+ }
+
+ this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
+
+ if (this.skippedCount === 1 && isDefined(dom.alert)) {
+ dom.alert.appendChild(this.skippedAlert);
+ }
+
+ // passing specs UI
+ if (isUndefined(this.passedAlert)) {
+ this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" });
+ }
+ this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
+
+ // failing specs UI
+ if (isUndefined(this.failedAlert)) {
+ this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
+ }
+ this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
+
+ if (this.failedCount === 1 && isDefined(dom.alert)) {
+ dom.alert.appendChild(this.failedAlert);
+ dom.alert.appendChild(this.resultsMenu);
+ }
+
+ // summary info
+ this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
+ this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
+ };
+
+ this.complete = function() {
+ dom.alert.removeChild(this.runningAlert);
+
+ this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
+
+ if (this.failedCount === 0) {
+ dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
+ } else {
+ showDetails();
+ }
+
+ dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
+ };
+
+ return this;
+
+ function showDetails() {
+ if (dom.reporter.className.search(/showDetails/) === -1) {
+ dom.reporter.className += " showDetails";
+ }
+ }
+
+ function isUndefined(obj) {
+ return typeof obj === 'undefined';
+ }
+
+ function isDefined(obj) {
+ return !isUndefined(obj);
+ }
+
+ function specPluralizedFor(count) {
+ var str = count + " spec";
+ if (count > 1) {
+ str += "s"
+ }
+ return str;
+ }
+
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
+
+
+jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
+ this.spec = spec;
+ this.dom = dom;
+ this.views = views;
+
+ this.symbol = this.createDom('li', { className: 'pending' });
+ this.dom.symbolSummary.appendChild(this.symbol);
+
+ this.summary = this.createDom('div', { className: 'specSummary' },
+ this.createDom('a', {
+ className: 'description',
+ href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
+ title: this.spec.getFullName()
+ }, this.spec.description)
+ );
+
+ this.detail = this.createDom('div', { className: 'specDetail' },
+ this.createDom('a', {
+ className: 'description',
+ href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
+ title: this.spec.getFullName()
+ }, this.spec.getFullName())
+ );
+};
+
+jasmine.HtmlReporter.SpecView.prototype.status = function() {
+ return this.getSpecStatus(this.spec);
+};
+
+jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
+ this.symbol.className = this.status();
+
+ switch (this.status()) {
+ case 'skipped':
+ break;
+
+ case 'passed':
+ this.appendSummaryToSuiteDiv();
+ break;
+
+ case 'failed':
+ this.appendSummaryToSuiteDiv();
+ this.appendFailureDetail();
+ break;
+ }
+};
+
+jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
+ this.summary.className += ' ' + this.status();
+ this.appendToSummary(this.spec, this.summary);
+};
+
+jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
+ this.detail.className += ' ' + this.status();
+
+ var resultItems = this.spec.results().getItems();
+ var messagesDiv = this.createDom('div', { className: 'messages' });
+
+ for (var i = 0; i < resultItems.length; i++) {
+ var result = resultItems[i];
+
+ if (result.type == 'log') {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+ } else if (result.type == 'expect' && result.passed && !result.passed()) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+ if (result.trace.stack) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+ }
+ }
+ }
+
+ if (messagesDiv.childNodes.length > 0) {
+ this.detail.appendChild(messagesDiv);
+ this.dom.details.appendChild(this.detail);
+ }
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
+ this.suite = suite;
+ this.dom = dom;
+ this.views = views;
+
+ this.element = this.createDom('div', { className: 'suite' },
+ this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description)
+ );
+
+ this.appendToSummary(this.suite, this.element);
+};
+
+jasmine.HtmlReporter.SuiteView.prototype.status = function() {
+ return this.getSpecStatus(this.suite);
+};
+
+jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
+ this.element.className += " " + this.status();
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
+
+/* @deprecated Use jasmine.HtmlReporter instead
+ */
+jasmine.TrivialReporter = function(doc) {
+ this.document = doc || document;
+ this.suiteDivs = {};
+ this.logRunningSpecs = false;
+};
+
+jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
+ var el = document.createElement(type);
+
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(document.createTextNode(child));
+ } else {
+ if (child) { el.appendChild(child); }
+ }
+ }
+
+ for (var attr in attrs) {
+ if (attr == "className") {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
+ var showPassed, showSkipped;
+
+ this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
+ this.createDom('div', { className: 'banner' },
+ this.createDom('div', { className: 'logo' },
+ this.createDom('span', { className: 'title' }, "Jasmine"),
+ this.createDom('span', { className: 'version' }, runner.env.versionString())),
+ this.createDom('div', { className: 'options' },
+ "Show ",
+ showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
+ showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
+ )
+ ),
+
+ this.runnerDiv = this.createDom('div', { className: 'runner running' },
+ this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
+ this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
+ this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
+ );
+
+ this.document.body.appendChild(this.outerDiv);
+
+ var suites = runner.suites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ var suiteDiv = this.createDom('div', { className: 'suite' },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
+ this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
+ this.suiteDivs[suite.id] = suiteDiv;
+ var parentDiv = this.outerDiv;
+ if (suite.parentSuite) {
+ parentDiv = this.suiteDivs[suite.parentSuite.id];
+ }
+ parentDiv.appendChild(suiteDiv);
+ }
+
+ this.startedAt = new Date();
+
+ var self = this;
+ showPassed.onclick = function(evt) {
+ if (showPassed.checked) {
+ self.outerDiv.className += ' show-passed';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
+ }
+ };
+
+ showSkipped.onclick = function(evt) {
+ if (showSkipped.checked) {
+ self.outerDiv.className += ' show-skipped';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
+ }
+ };
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
+ var results = runner.results();
+ var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
+ this.runnerDiv.setAttribute("class", className);
+ //do it twice for IE
+ this.runnerDiv.setAttribute("className", className);
+ var specs = runner.specs();
+ var specCount = 0;
+ for (var i = 0; i < specs.length; i++) {
+ if (this.specFilter(specs[i])) {
+ specCount++;
+ }
+ }
+ var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
+ message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
+ this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
+
+ this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
+};
+
+jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
+ var results = suite.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.totalCount === 0) { // todo: change this to check results.skipped
+ status = 'skipped';
+ }
+ this.suiteDivs[suite.id].className += " " + status;
+};
+
+jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
+ if (this.logRunningSpecs) {
+ this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+ }
+};
+
+jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
+ var results = spec.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.skipped) {
+ status = 'skipped';
+ }
+ var specDiv = this.createDom('div', { className: 'spec ' + status },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
+ this.createDom('a', {
+ className: 'description',
+ href: '?spec=' + encodeURIComponent(spec.getFullName()),
+ title: spec.getFullName()
+ }, spec.description));
+
+
+ var resultItems = results.getItems();
+ var messagesDiv = this.createDom('div', { className: 'messages' });
+ for (var i = 0; i < resultItems.length; i++) {
+ var result = resultItems[i];
+
+ if (result.type == 'log') {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+ } else if (result.type == 'expect' && result.passed && !result.passed()) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+ if (result.trace.stack) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+ }
+ }
+ }
+
+ if (messagesDiv.childNodes.length > 0) {
+ specDiv.appendChild(messagesDiv);
+ }
+
+ this.suiteDivs[spec.suite.id].appendChild(specDiv);
+};
+
+jasmine.TrivialReporter.prototype.log = function() {
+ var console = jasmine.getGlobal().console;
+ if (console && console.log) {
+ if (console.log.apply) {
+ console.log.apply(console, arguments);
+ } else {
+ console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+ }
+ }
+};
+
+jasmine.TrivialReporter.prototype.getLocation = function() {
+ return this.document.location;
+};
+
+jasmine.TrivialReporter.prototype.specFilter = function(spec) {
+ var paramMap = {};
+ var params = this.getLocation().search.substring(1).split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+ }
+
+ if (!paramMap.spec) {
+ return true;
+ }
+ return spec.getFullName().indexOf(paramMap.spec) === 0;
+};
--- /dev/null
+body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
+
+#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
+#HTMLReporter a { text-decoration: none; }
+#HTMLReporter a:hover { text-decoration: underline; }
+#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
+#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
+#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
+#HTMLReporter .version { color: #aaaaaa; }
+#HTMLReporter .banner { margin-top: 14px; }
+#HTMLReporter .duration { color: #aaaaaa; float: right; }
+#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
+#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
+#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
+#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
+#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
+#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
+#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
+#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
+#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
+#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
+#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
+#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
+#HTMLReporter .runningAlert { background-color: #666666; }
+#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
+#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
+#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
+#HTMLReporter .passingAlert { background-color: #a6b779; }
+#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
+#HTMLReporter .failingAlert { background-color: #cf867e; }
+#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
+#HTMLReporter .results { margin-top: 14px; }
+#HTMLReporter #details { display: none; }
+#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
+#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
+#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
+#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
+#HTMLReporter.showDetails .summary { display: none; }
+#HTMLReporter.showDetails #details { display: block; }
+#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
+#HTMLReporter .summary { margin-top: 14px; }
+#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
+#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
+#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
+#HTMLReporter .description + .suite { margin-top: 0; }
+#HTMLReporter .suite { margin-top: 14px; }
+#HTMLReporter .suite a { color: #333333; }
+#HTMLReporter #details .specDetail { margin-bottom: 28px; }
+#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
+#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
+#HTMLReporter .resultMessage span.result { display: block; }
+#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
+
+#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
+#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
+#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
+#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
+#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
+#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
+#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
+#TrivialReporter .runner.running { background-color: yellow; }
+#TrivialReporter .options { text-align: right; font-size: .8em; }
+#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
+#TrivialReporter .suite .suite { margin: 5px; }
+#TrivialReporter .suite.passed { background-color: #dfd; }
+#TrivialReporter .suite.failed { background-color: #fdd; }
+#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
+#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
+#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
+#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
+#TrivialReporter .spec.skipped { background-color: #bbb; }
+#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
+#TrivialReporter .passed { background-color: #cfc; display: none; }
+#TrivialReporter .failed { background-color: #fbb; }
+#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
+#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
+#TrivialReporter .resultMessage .mismatch { color: black; }
+#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
+#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
+#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
+#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
+#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
--- /dev/null
+var isCommonJS = typeof window == "undefined" && typeof exports == "object";
+
+/**
+ * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
+ *
+ * @namespace
+ */
+var jasmine = {};
+if (isCommonJS) exports.jasmine = jasmine;
+/**
+ * @private
+ */
+jasmine.unimplementedMethod_ = function() {
+ throw new Error("unimplemented method");
+};
+
+/**
+ * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
+ * a plain old variable and may be redefined by somebody else.
+ *
+ * @private
+ */
+jasmine.undefined = jasmine.___undefined___;
+
+/**
+ * Show diagnostic messages in the console if set to true
+ *
+ */
+jasmine.VERBOSE = false;
+
+/**
+ * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
+ *
+ */
+jasmine.DEFAULT_UPDATE_INTERVAL = 250;
+
+/**
+ * Maximum levels of nesting that will be included when an object is pretty-printed
+ */
+jasmine.MAX_PRETTY_PRINT_DEPTH = 40;
+
+/**
+ * Default timeout interval in milliseconds for waitsFor() blocks.
+ */
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+/**
+ * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite.
+ * Set to false to let the exception bubble up in the browser.
+ *
+ */
+jasmine.CATCH_EXCEPTIONS = true;
+
+jasmine.getGlobal = function() {
+ function getGlobal() {
+ return this;
+ }
+
+ return getGlobal();
+};
+
+/**
+ * Allows for bound functions to be compared. Internal use only.
+ *
+ * @ignore
+ * @private
+ * @param base {Object} bound 'this' for the function
+ * @param name {Function} function to find
+ */
+jasmine.bindOriginal_ = function(base, name) {
+ var original = base[name];
+ if (original.apply) {
+ return function() {
+ return original.apply(base, arguments);
+ };
+ } else {
+ // IE support
+ return jasmine.getGlobal()[name];
+ }
+};
+
+jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
+jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
+jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
+jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
+
+jasmine.MessageResult = function(values) {
+ this.type = 'log';
+ this.values = values;
+ this.trace = new Error(); // todo: test better
+};
+
+jasmine.MessageResult.prototype.toString = function() {
+ var text = "";
+ for (var i = 0; i < this.values.length; i++) {
+ if (i > 0) text += " ";
+ if (jasmine.isString_(this.values[i])) {
+ text += this.values[i];
+ } else {
+ text += jasmine.pp(this.values[i]);
+ }
+ }
+ return text;
+};
+
+jasmine.ExpectationResult = function(params) {
+ this.type = 'expect';
+ this.matcherName = params.matcherName;
+ this.passed_ = params.passed;
+ this.expected = params.expected;
+ this.actual = params.actual;
+ this.message = this.passed_ ? 'Passed.' : params.message;
+
+ var trace = (params.trace || new Error(this.message));
+ this.trace = this.passed_ ? '' : trace;
+};
+
+jasmine.ExpectationResult.prototype.toString = function () {
+ return this.message;
+};
+
+jasmine.ExpectationResult.prototype.passed = function () {
+ return this.passed_;
+};
+
+/**
+ * Getter for the Jasmine environment. Ensures one gets created
+ */
+jasmine.getEnv = function() {
+ var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
+ return env;
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isArray_ = function(value) {
+ return jasmine.isA_("Array", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isString_ = function(value) {
+ return jasmine.isA_("String", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isNumber_ = function(value) {
+ return jasmine.isA_("Number", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param {String} typeName
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isA_ = function(typeName, value) {
+ return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+};
+
+/**
+ * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
+ *
+ * @param value {Object} an object to be outputted
+ * @returns {String}
+ */
+jasmine.pp = function(value) {
+ var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
+ stringPrettyPrinter.format(value);
+ return stringPrettyPrinter.string;
+};
+
+/**
+ * Returns true if the object is a DOM Node.
+ *
+ * @param {Object} obj object to check
+ * @returns {Boolean}
+ */
+jasmine.isDomNode = function(obj) {
+ return obj.nodeType > 0;
+};
+
+/**
+ * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
+ *
+ * @example
+ * // don't care about which function is passed in, as long as it's a function
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
+ *
+ * @param {Class} clazz
+ * @returns matchable object of the type clazz
+ */
+jasmine.any = function(clazz) {
+ return new jasmine.Matchers.Any(clazz);
+};
+
+/**
+ * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
+ * attributes on the object.
+ *
+ * @example
+ * // don't care about any other attributes than foo.
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
+ *
+ * @param sample {Object} sample
+ * @returns matchable object for the sample
+ */
+jasmine.objectContaining = function (sample) {
+ return new jasmine.Matchers.ObjectContaining(sample);
+};
+
+/**
+ * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
+ *
+ * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
+ * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
+ *
+ * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
+ *
+ * Spies are torn down at the end of every spec.
+ *
+ * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
+ *
+ * @example
+ * // a stub
+ * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
+ *
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // actual foo.not will not be called, execution stops
+ * spyOn(foo, 'not');
+
+ // foo.not spied upon, execution will continue to implementation
+ * spyOn(foo, 'not').andCallThrough();
+ *
+ * // fake example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // foo.not(val) will return val
+ * spyOn(foo, 'not').andCallFake(function(value) {return value;});
+ *
+ * // mock example
+ * foo.not(7 == 7);
+ * expect(foo.not).toHaveBeenCalled();
+ * expect(foo.not).toHaveBeenCalledWith(true);
+ *
+ * @constructor
+ * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
+ * @param {String} name
+ */
+jasmine.Spy = function(name) {
+ /**
+ * The name of the spy, if provided.
+ */
+ this.identity = name || 'unknown';
+ /**
+ * Is this Object a spy?
+ */
+ this.isSpy = true;
+ /**
+ * The actual function this spy stubs.
+ */
+ this.plan = function() {
+ };
+ /**
+ * Tracking of the most recent call to the spy.
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy.mostRecentCall.args = [1, 2];
+ */
+ this.mostRecentCall = {};
+
+ /**
+ * Holds arguments for each call to the spy, indexed by call count
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy(7, 8);
+ * mySpy.mostRecentCall.args = [7, 8];
+ * mySpy.argsForCall[0] = [1, 2];
+ * mySpy.argsForCall[1] = [7, 8];
+ */
+ this.argsForCall = [];
+ this.calls = [];
+};
+
+/**
+ * Tells a spy to call through to the actual implemenatation.
+ *
+ * @example
+ * var foo = {
+ * bar: function() { // do some stuff }
+ * }
+ *
+ * // defining a spy on an existing property: foo.bar
+ * spyOn(foo, 'bar').andCallThrough();
+ */
+jasmine.Spy.prototype.andCallThrough = function() {
+ this.plan = this.originalValue;
+ return this;
+};
+
+/**
+ * For setting the return value of a spy.
+ *
+ * @example
+ * // defining a spy from scratch: foo() returns 'baz'
+ * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() returns 'baz'
+ * spyOn(foo, 'bar').andReturn('baz');
+ *
+ * @param {Object} value
+ */
+jasmine.Spy.prototype.andReturn = function(value) {
+ this.plan = function() {
+ return value;
+ };
+ return this;
+};
+
+/**
+ * For throwing an exception when a spy is called.
+ *
+ * @example
+ * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
+ * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
+ * spyOn(foo, 'bar').andThrow('baz');
+ *
+ * @param {String} exceptionMsg
+ */
+jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
+ this.plan = function() {
+ throw exceptionMsg;
+ };
+ return this;
+};
+
+/**
+ * Calls an alternate implementation when a spy is called.
+ *
+ * @example
+ * var baz = function() {
+ * // do some stuff, return something
+ * }
+ * // defining a spy from scratch: foo() calls the function baz
+ * var foo = jasmine.createSpy('spy on foo').andCall(baz);
+ *
+ * // defining a spy on an existing property: foo.bar() calls an anonymnous function
+ * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
+ *
+ * @param {Function} fakeFunc
+ */
+jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
+ this.plan = fakeFunc;
+ return this;
+};
+
+/**
+ * Resets all of a spy's the tracking variables so that it can be used again.
+ *
+ * @example
+ * spyOn(foo, 'bar');
+ *
+ * foo.bar();
+ *
+ * expect(foo.bar.callCount).toEqual(1);
+ *
+ * foo.bar.reset();
+ *
+ * expect(foo.bar.callCount).toEqual(0);
+ */
+jasmine.Spy.prototype.reset = function() {
+ this.wasCalled = false;
+ this.callCount = 0;
+ this.argsForCall = [];
+ this.calls = [];
+ this.mostRecentCall = {};
+};
+
+jasmine.createSpy = function(name) {
+
+ var spyObj = function() {
+ spyObj.wasCalled = true;
+ spyObj.callCount++;
+ var args = jasmine.util.argsToArray(arguments);
+ spyObj.mostRecentCall.object = this;
+ spyObj.mostRecentCall.args = args;
+ spyObj.argsForCall.push(args);
+ spyObj.calls.push({object: this, args: args});
+ return spyObj.plan.apply(this, arguments);
+ };
+
+ var spy = new jasmine.Spy(name);
+
+ for (var prop in spy) {
+ spyObj[prop] = spy[prop];
+ }
+
+ spyObj.reset();
+
+ return spyObj;
+};
+
+/**
+ * Determines whether an object is a spy.
+ *
+ * @param {jasmine.Spy|Object} putativeSpy
+ * @returns {Boolean}
+ */
+jasmine.isSpy = function(putativeSpy) {
+ return putativeSpy && putativeSpy.isSpy;
+};
+
+/**
+ * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
+ * large in one call.
+ *
+ * @param {String} baseName name of spy class
+ * @param {Array} methodNames array of names of methods to make spies
+ */
+jasmine.createSpyObj = function(baseName, methodNames) {
+ if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
+ throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
+ }
+ var obj = {};
+ for (var i = 0; i < methodNames.length; i++) {
+ obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
+ }
+ return obj;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
+ *
+ * Be careful not to leave calls to <code>jasmine.log</code> in production code.
+ */
+jasmine.log = function() {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.log.apply(spec, arguments);
+};
+
+/**
+ * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
+ *
+ * @example
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
+ *
+ * @see jasmine.createSpy
+ * @param obj
+ * @param methodName
+ * @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods
+ */
+var spyOn = function(obj, methodName) {
+ return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
+};
+if (isCommonJS) exports.spyOn = spyOn;
+
+/**
+ * Creates a Jasmine spec that will be added to the current suite.
+ *
+ * // TODO: pending tests
+ *
+ * @example
+ * it('should be true', function() {
+ * expect(true).toEqual(true);
+ * });
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var it = function(desc, func) {
+ return jasmine.getEnv().it(desc, func);
+};
+if (isCommonJS) exports.it = it;
+
+/**
+ * Creates a <em>disabled</em> Jasmine spec.
+ *
+ * A convenience method that allows existing specs to be disabled temporarily during development.
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var xit = function(desc, func) {
+ return jasmine.getEnv().xit(desc, func);
+};
+if (isCommonJS) exports.xit = xit;
+
+/**
+ * Starts a chain for a Jasmine expectation.
+ *
+ * It is passed an Object that is the actual value and should chain to one of the many
+ * jasmine.Matchers functions.
+ *
+ * @param {Object} actual Actual value to test against and expected value
+ * @return {jasmine.Matchers}
+ */
+var expect = function(actual) {
+ return jasmine.getEnv().currentSpec.expect(actual);
+};
+if (isCommonJS) exports.expect = expect;
+
+/**
+ * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
+ *
+ * @param {Function} func Function that defines part of a jasmine spec.
+ */
+var runs = function(func) {
+ jasmine.getEnv().currentSpec.runs(func);
+};
+if (isCommonJS) exports.runs = runs;
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+var waits = function(timeout) {
+ jasmine.getEnv().currentSpec.waits(timeout);
+};
+if (isCommonJS) exports.waits = waits;
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
+};
+if (isCommonJS) exports.waitsFor = waitsFor;
+
+/**
+ * A function that is called before each spec in a suite.
+ *
+ * Used for spec setup, including validating assumptions.
+ *
+ * @param {Function} beforeEachFunction
+ */
+var beforeEach = function(beforeEachFunction) {
+ jasmine.getEnv().beforeEach(beforeEachFunction);
+};
+if (isCommonJS) exports.beforeEach = beforeEach;
+
+/**
+ * A function that is called after each spec in a suite.
+ *
+ * Used for restoring any state that is hijacked during spec execution.
+ *
+ * @param {Function} afterEachFunction
+ */
+var afterEach = function(afterEachFunction) {
+ jasmine.getEnv().afterEach(afterEachFunction);
+};
+if (isCommonJS) exports.afterEach = afterEach;
+
+/**
+ * Defines a suite of specifications.
+ *
+ * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
+ * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
+ * of setup in some tests.
+ *
+ * @example
+ * // TODO: a simple suite
+ *
+ * // TODO: a simple suite with a nested describe block
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var describe = function(description, specDefinitions) {
+ return jasmine.getEnv().describe(description, specDefinitions);
+};
+if (isCommonJS) exports.describe = describe;
+
+/**
+ * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var xdescribe = function(description, specDefinitions) {
+ return jasmine.getEnv().xdescribe(description, specDefinitions);
+};
+if (isCommonJS) exports.xdescribe = xdescribe;
+
+
+// Provide the XMLHttpRequest class for IE 5.x-6.x:
+jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
+ function tryIt(f) {
+ try {
+ return f();
+ } catch(e) {
+ }
+ return null;
+ }
+
+ var xhr = tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP.6.0");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP.3.0");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ });
+
+ if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
+
+ return xhr;
+} : XMLHttpRequest;
+/**
+ * @namespace
+ */
+jasmine.util = {};
+
+/**
+ * Declare that a child class inherit it's prototype from the parent class.
+ *
+ * @private
+ * @param {Function} childClass
+ * @param {Function} parentClass
+ */
+jasmine.util.inherit = function(childClass, parentClass) {
+ /**
+ * @private
+ */
+ var subclass = function() {
+ };
+ subclass.prototype = parentClass.prototype;
+ childClass.prototype = new subclass();
+};
+
+jasmine.util.formatException = function(e) {
+ var lineNumber;
+ if (e.line) {
+ lineNumber = e.line;
+ }
+ else if (e.lineNumber) {
+ lineNumber = e.lineNumber;
+ }
+
+ var file;
+
+ if (e.sourceURL) {
+ file = e.sourceURL;
+ }
+ else if (e.fileName) {
+ file = e.fileName;
+ }
+
+ var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
+
+ if (file && lineNumber) {
+ message += ' in ' + file + ' (line ' + lineNumber + ')';
+ }
+
+ return message;
+};
+
+jasmine.util.htmlEscape = function(str) {
+ if (!str) return str;
+ return str.replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>');
+};
+
+jasmine.util.argsToArray = function(args) {
+ var arrayOfArgs = [];
+ for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
+ return arrayOfArgs;
+};
+
+jasmine.util.extend = function(destination, source) {
+ for (var property in source) destination[property] = source[property];
+ return destination;
+};
+
+/**
+ * Environment for Jasmine
+ *
+ * @constructor
+ */
+jasmine.Env = function() {
+ this.currentSpec = null;
+ this.currentSuite = null;
+ this.currentRunner_ = new jasmine.Runner(this);
+
+ this.reporter = new jasmine.MultiReporter();
+
+ this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
+ this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
+ this.lastUpdate = 0;
+ this.specFilter = function() {
+ return true;
+ };
+
+ this.nextSpecId_ = 0;
+ this.nextSuiteId_ = 0;
+ this.equalityTesters_ = [];
+
+ // wrap matchers
+ this.matchersClass = function() {
+ jasmine.Matchers.apply(this, arguments);
+ };
+ jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
+
+ jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
+};
+
+
+jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
+jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
+jasmine.Env.prototype.setInterval = jasmine.setInterval;
+jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
+
+/**
+ * @returns an object containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.version = function () {
+ if (jasmine.version_) {
+ return jasmine.version_;
+ } else {
+ throw new Error('Version not set');
+ }
+};
+
+/**
+ * @returns string containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.versionString = function() {
+ if (!jasmine.version_) {
+ return "version unknown";
+ }
+
+ var version = this.version();
+ var versionString = version.major + "." + version.minor + "." + version.build;
+ if (version.release_candidate) {
+ versionString += ".rc" + version.release_candidate;
+ }
+ versionString += " revision " + version.revision;
+ return versionString;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSpecId = function () {
+ return this.nextSpecId_++;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSuiteId = function () {
+ return this.nextSuiteId_++;
+};
+
+/**
+ * Register a reporter to receive status updates from Jasmine.
+ * @param {jasmine.Reporter} reporter An object which will receive status updates.
+ */
+jasmine.Env.prototype.addReporter = function(reporter) {
+ this.reporter.addReporter(reporter);
+};
+
+jasmine.Env.prototype.execute = function() {
+ this.currentRunner_.execute();
+};
+
+jasmine.Env.prototype.describe = function(description, specDefinitions) {
+ var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
+
+ var parentSuite = this.currentSuite;
+ if (parentSuite) {
+ parentSuite.add(suite);
+ } else {
+ this.currentRunner_.add(suite);
+ }
+
+ this.currentSuite = suite;
+
+ var declarationError = null;
+ try {
+ specDefinitions.call(suite);
+ } catch(e) {
+ declarationError = e;
+ }
+
+ if (declarationError) {
+ this.it("encountered a declaration exception", function() {
+ throw declarationError;
+ });
+ }
+
+ this.currentSuite = parentSuite;
+
+ return suite;
+};
+
+jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.beforeEach(beforeEachFunction);
+ } else {
+ this.currentRunner_.beforeEach(beforeEachFunction);
+ }
+};
+
+jasmine.Env.prototype.currentRunner = function () {
+ return this.currentRunner_;
+};
+
+jasmine.Env.prototype.afterEach = function(afterEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.afterEach(afterEachFunction);
+ } else {
+ this.currentRunner_.afterEach(afterEachFunction);
+ }
+
+};
+
+jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
+ return {
+ execute: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.it = function(description, func) {
+ var spec = new jasmine.Spec(this, this.currentSuite, description);
+ this.currentSuite.add(spec);
+ this.currentSpec = spec;
+
+ if (func) {
+ spec.runs(func);
+ }
+
+ return spec;
+};
+
+jasmine.Env.prototype.xit = function(desc, func) {
+ return {
+ id: this.nextSpecId(),
+ runs: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) {
+ if (a.source != b.source)
+ mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/");
+
+ if (a.ignoreCase != b.ignoreCase)
+ mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier");
+
+ if (a.global != b.global)
+ mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier");
+
+ if (a.multiline != b.multiline)
+ mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier");
+
+ if (a.sticky != b.sticky)
+ mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier");
+
+ return (mismatchValues.length === 0);
+};
+
+jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
+ if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
+ return true;
+ }
+
+ a.__Jasmine_been_here_before__ = b;
+ b.__Jasmine_been_here_before__ = a;
+
+ var hasKey = function(obj, keyName) {
+ return obj !== null && obj[keyName] !== jasmine.undefined;
+ };
+
+ for (var property in b) {
+ if (!hasKey(a, property) && hasKey(b, property)) {
+ mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+ }
+ }
+ for (property in a) {
+ if (!hasKey(b, property) && hasKey(a, property)) {
+ mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
+ }
+ }
+ for (property in b) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
+ mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
+ }
+ }
+
+ if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
+ mismatchValues.push("arrays were not the same length");
+ }
+
+ delete a.__Jasmine_been_here_before__;
+ delete b.__Jasmine_been_here_before__;
+ return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+};
+
+jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
+ mismatchKeys = mismatchKeys || [];
+ mismatchValues = mismatchValues || [];
+
+ for (var i = 0; i < this.equalityTesters_.length; i++) {
+ var equalityTester = this.equalityTesters_[i];
+ var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
+ if (result !== jasmine.undefined) return result;
+ }
+
+ if (a === b) return true;
+
+ if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
+ return (a == jasmine.undefined && b == jasmine.undefined);
+ }
+
+ if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
+ return a === b;
+ }
+
+ if (a instanceof Date && b instanceof Date) {
+ return a.getTime() == b.getTime();
+ }
+
+ if (a.jasmineMatches) {
+ return a.jasmineMatches(b);
+ }
+
+ if (b.jasmineMatches) {
+ return b.jasmineMatches(a);
+ }
+
+ if (a instanceof jasmine.Matchers.ObjectContaining) {
+ return a.matches(b);
+ }
+
+ if (b instanceof jasmine.Matchers.ObjectContaining) {
+ return b.matches(a);
+ }
+
+ if (jasmine.isString_(a) && jasmine.isString_(b)) {
+ return (a == b);
+ }
+
+ if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
+ return (a == b);
+ }
+
+ if (a instanceof RegExp && b instanceof RegExp) {
+ return this.compareRegExps_(a, b, mismatchKeys, mismatchValues);
+ }
+
+ if (typeof a === "object" && typeof b === "object") {
+ return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
+ }
+
+ //Straight check
+ return (a === b);
+};
+
+jasmine.Env.prototype.contains_ = function(haystack, needle) {
+ if (jasmine.isArray_(haystack)) {
+ for (var i = 0; i < haystack.length; i++) {
+ if (this.equals_(haystack[i], needle)) return true;
+ }
+ return false;
+ }
+ return haystack.indexOf(needle) >= 0;
+};
+
+jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
+ this.equalityTesters_.push(equalityTester);
+};
+/** No-op base class for Jasmine reporters.
+ *
+ * @constructor
+ */
+jasmine.Reporter = function() {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecResults = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.log = function(str) {
+};
+
+/**
+ * Blocks are functions with executable code that make up a spec.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {Function} func
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Block = function(env, func, spec) {
+ this.env = env;
+ this.func = func;
+ this.spec = spec;
+};
+
+jasmine.Block.prototype.execute = function(onComplete) {
+ if (!jasmine.CATCH_EXCEPTIONS) {
+ this.func.apply(this.spec);
+ }
+ else {
+ try {
+ this.func.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ }
+ }
+ onComplete();
+};
+/** JavaScript API reporter.
+ *
+ * @constructor
+ */
+jasmine.JsApiReporter = function() {
+ this.started = false;
+ this.finished = false;
+ this.suites_ = [];
+ this.results_ = {};
+};
+
+jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
+ this.started = true;
+ var suites = runner.topLevelSuites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ this.suites_.push(this.summarize_(suite));
+ }
+};
+
+jasmine.JsApiReporter.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
+ var isSuite = suiteOrSpec instanceof jasmine.Suite;
+ var summary = {
+ id: suiteOrSpec.id,
+ name: suiteOrSpec.description,
+ type: isSuite ? 'suite' : 'spec',
+ children: []
+ };
+
+ if (isSuite) {
+ var children = suiteOrSpec.children();
+ for (var i = 0; i < children.length; i++) {
+ summary.children.push(this.summarize_(children[i]));
+ }
+ }
+ return summary;
+};
+
+jasmine.JsApiReporter.prototype.results = function() {
+ return this.results_;
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
+ return this.results_[specId];
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
+ this.finished = true;
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
+ this.results_[spec.id] = {
+ messages: spec.results().getItems(),
+ result: spec.results().failedCount > 0 ? "failed" : "passed"
+ };
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.log = function(str) {
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
+ var results = {};
+ for (var i = 0; i < specIds.length; i++) {
+ var specId = specIds[i];
+ results[specId] = this.summarizeResult_(this.results_[specId]);
+ }
+ return results;
+};
+
+jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
+ var summaryMessages = [];
+ var messagesLength = result.messages.length;
+ for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
+ var resultMessage = result.messages[messageIndex];
+ summaryMessages.push({
+ text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
+ passed: resultMessage.passed ? resultMessage.passed() : true,
+ type: resultMessage.type,
+ message: resultMessage.message,
+ trace: {
+ stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
+ }
+ });
+ }
+
+ return {
+ result : result.result,
+ messages : summaryMessages
+ };
+};
+
+/**
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param actual
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Matchers = function(env, actual, spec, opt_isNot) {
+ this.env = env;
+ this.actual = actual;
+ this.spec = spec;
+ this.isNot = opt_isNot || false;
+ this.reportWasCalled_ = false;
+};
+
+// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
+jasmine.Matchers.pp = function(str) {
+ throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
+};
+
+// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
+jasmine.Matchers.prototype.report = function(result, failing_message, details) {
+ throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
+};
+
+jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
+ for (var methodName in prototype) {
+ if (methodName == 'report') continue;
+ var orig = prototype[methodName];
+ matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
+ }
+};
+
+jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
+ return function() {
+ var matcherArgs = jasmine.util.argsToArray(arguments);
+ var result = matcherFunction.apply(this, arguments);
+
+ if (this.isNot) {
+ result = !result;
+ }
+
+ if (this.reportWasCalled_) return result;
+
+ var message;
+ if (!result) {
+ if (this.message) {
+ message = this.message.apply(this, arguments);
+ if (jasmine.isArray_(message)) {
+ message = message[this.isNot ? 1 : 0];
+ }
+ } else {
+ var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+ message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
+ if (matcherArgs.length > 0) {
+ for (var i = 0; i < matcherArgs.length; i++) {
+ if (i > 0) message += ",";
+ message += " " + jasmine.pp(matcherArgs[i]);
+ }
+ }
+ message += ".";
+ }
+ }
+ var expectationResult = new jasmine.ExpectationResult({
+ matcherName: matcherName,
+ passed: result,
+ expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
+ actual: this.actual,
+ message: message
+ });
+ this.spec.addMatcherResult(expectationResult);
+ return jasmine.undefined;
+ };
+};
+
+
+
+
+/**
+ * toBe: compares the actual to the expected using ===
+ * @param expected
+ */
+jasmine.Matchers.prototype.toBe = function(expected) {
+ return this.actual === expected;
+};
+
+/**
+ * toNotBe: compares the actual to the expected using !==
+ * @param expected
+ * @deprecated as of 1.0. Use not.toBe() instead.
+ */
+jasmine.Matchers.prototype.toNotBe = function(expected) {
+ return this.actual !== expected;
+};
+
+/**
+ * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toEqual = function(expected) {
+ return this.env.equals_(this.actual, expected);
+};
+
+/**
+ * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
+ * @param expected
+ * @deprecated as of 1.0. Use not.toEqual() instead.
+ */
+jasmine.Matchers.prototype.toNotEqual = function(expected) {
+ return !this.env.equals_(this.actual, expected);
+};
+
+/**
+ * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
+ * a pattern or a String.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toMatch = function(expected) {
+ return new RegExp(expected).test(this.actual);
+};
+
+/**
+ * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
+ * @param expected
+ * @deprecated as of 1.0. Use not.toMatch() instead.
+ */
+jasmine.Matchers.prototype.toNotMatch = function(expected) {
+ return !(new RegExp(expected).test(this.actual));
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeDefined = function() {
+ return (this.actual !== jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeUndefined = function() {
+ return (this.actual === jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to null.
+ */
+jasmine.Matchers.prototype.toBeNull = function() {
+ return (this.actual === null);
+};
+
+/**
+ * Matcher that compares the actual to NaN.
+ */
+jasmine.Matchers.prototype.toBeNaN = function() {
+ this.message = function() {
+ return [ "Expected " + jasmine.pp(this.actual) + " to be NaN." ];
+ };
+
+ return (this.actual !== this.actual);
+};
+
+/**
+ * Matcher that boolean not-nots the actual.
+ */
+jasmine.Matchers.prototype.toBeTruthy = function() {
+ return !!this.actual;
+};
+
+
+/**
+ * Matcher that boolean nots the actual.
+ */
+jasmine.Matchers.prototype.toBeFalsy = function() {
+ return !this.actual;
+};
+
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called.
+ */
+jasmine.Matchers.prototype.toHaveBeenCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to have been called.",
+ "Expected spy " + this.actual.identity + " not to have been called."
+ ];
+ };
+
+ return this.actual.wasCalled;
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
+jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was not called.
+ *
+ * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
+ */
+jasmine.Matchers.prototype.wasNotCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('wasNotCalled does not take arguments');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to not have been called.",
+ "Expected spy " + this.actual.identity + " to have been called."
+ ];
+ };
+
+ return !this.actual.wasCalled;
+};
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
+ *
+ * @example
+ *
+ */
+jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+ this.message = function() {
+ var invertedMessage = "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was.";
+ var positiveMessage = "";
+ if (this.actual.callCount === 0) {
+ positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.";
+ } else {
+ positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but actual calls were " + jasmine.pp(this.actual.argsForCall).replace(/^\[ | \]$/g, '')
+ }
+ return [positiveMessage, invertedMessage];
+ };
+
+ return this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
+
+/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasNotCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
+ "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
+ ];
+ };
+
+ return !this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/**
+ * Matcher that checks that the expected item is an element in the actual Array.
+ *
+ * @param {Object} expected
+ */
+jasmine.Matchers.prototype.toContain = function(expected) {
+ return this.env.contains_(this.actual, expected);
+};
+
+/**
+ * Matcher that checks that the expected item is NOT an element in the actual Array.
+ *
+ * @param {Object} expected
+ * @deprecated as of 1.0. Use not.toContain() instead.
+ */
+jasmine.Matchers.prototype.toNotContain = function(expected) {
+ return !this.env.contains_(this.actual, expected);
+};
+
+jasmine.Matchers.prototype.toBeLessThan = function(expected) {
+ return this.actual < expected;
+};
+
+jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
+ return this.actual > expected;
+};
+
+/**
+ * Matcher that checks that the expected item is equal to the actual item
+ * up to a given level of decimal precision (default 2).
+ *
+ * @param {Number} expected
+ * @param {Number} precision, as number of decimal places
+ */
+jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
+ if (!(precision === 0)) {
+ precision = precision || 2;
+ }
+ return Math.abs(expected - this.actual) < (Math.pow(10, -precision) / 2);
+};
+
+/**
+ * Matcher that checks that the expected exception was thrown by the actual.
+ *
+ * @param {String} [expected]
+ */
+jasmine.Matchers.prototype.toThrow = function(expected) {
+ var result = false;
+ var exception;
+ if (typeof this.actual != 'function') {
+ throw new Error('Actual is not a function');
+ }
+ try {
+ this.actual();
+ } catch (e) {
+ exception = e;
+ }
+ if (exception) {
+ result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
+ }
+
+ var not = this.isNot ? "not " : "";
+
+ this.message = function() {
+ if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
+ return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
+ } else {
+ return "Expected function to throw an exception.";
+ }
+ };
+
+ return result;
+};
+
+jasmine.Matchers.Any = function(expectedClass) {
+ this.expectedClass = expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.jasmineMatches = function(other) {
+ if (this.expectedClass == String) {
+ return typeof other == 'string' || other instanceof String;
+ }
+
+ if (this.expectedClass == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
+
+ if (this.expectedClass == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
+
+ if (this.expectedClass == Object) {
+ return typeof other == 'object';
+ }
+
+ return other instanceof this.expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.jasmineToString = function() {
+ return '<jasmine.any(' + this.expectedClass + ')>';
+};
+
+jasmine.Matchers.ObjectContaining = function (sample) {
+ this.sample = sample;
+};
+
+jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
+ mismatchKeys = mismatchKeys || [];
+ mismatchValues = mismatchValues || [];
+
+ var env = jasmine.getEnv();
+
+ var hasKey = function(obj, keyName) {
+ return obj != null && obj[keyName] !== jasmine.undefined;
+ };
+
+ for (var property in this.sample) {
+ if (!hasKey(other, property) && hasKey(this.sample, property)) {
+ mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+ }
+ else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) {
+ mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual.");
+ }
+ }
+
+ return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+};
+
+jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () {
+ return "<jasmine.objectContaining(" + jasmine.pp(this.sample) + ")>";
+};
+// Mock setTimeout, clearTimeout
+// Contributed by Pivotal Computer Systems, www.pivotalsf.com
+
+jasmine.FakeTimer = function() {
+ this.reset();
+
+ var self = this;
+ self.setTimeout = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
+ return self.timeoutsMade;
+ };
+
+ self.setInterval = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
+ return self.timeoutsMade;
+ };
+
+ self.clearTimeout = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+ self.clearInterval = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+};
+
+jasmine.FakeTimer.prototype.reset = function() {
+ this.timeoutsMade = 0;
+ this.scheduledFunctions = {};
+ this.nowMillis = 0;
+};
+
+jasmine.FakeTimer.prototype.tick = function(millis) {
+ var oldMillis = this.nowMillis;
+ var newMillis = oldMillis + millis;
+ this.runFunctionsWithinRange(oldMillis, newMillis);
+ this.nowMillis = newMillis;
+};
+
+jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
+ var scheduledFunc;
+ var funcsToRun = [];
+ for (var timeoutKey in this.scheduledFunctions) {
+ scheduledFunc = this.scheduledFunctions[timeoutKey];
+ if (scheduledFunc != jasmine.undefined &&
+ scheduledFunc.runAtMillis >= oldMillis &&
+ scheduledFunc.runAtMillis <= nowMillis) {
+ funcsToRun.push(scheduledFunc);
+ this.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ }
+ }
+
+ if (funcsToRun.length > 0) {
+ funcsToRun.sort(function(a, b) {
+ return a.runAtMillis - b.runAtMillis;
+ });
+ for (var i = 0; i < funcsToRun.length; ++i) {
+ try {
+ var funcToRun = funcsToRun[i];
+ this.nowMillis = funcToRun.runAtMillis;
+ funcToRun.funcToCall();
+ if (funcToRun.recurring) {
+ this.scheduleFunction(funcToRun.timeoutKey,
+ funcToRun.funcToCall,
+ funcToRun.millis,
+ true);
+ }
+ } catch(e) {
+ }
+ }
+ this.runFunctionsWithinRange(oldMillis, nowMillis);
+ }
+};
+
+jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
+ this.scheduledFunctions[timeoutKey] = {
+ runAtMillis: this.nowMillis + millis,
+ funcToCall: funcToCall,
+ recurring: recurring,
+ timeoutKey: timeoutKey,
+ millis: millis
+ };
+};
+
+/**
+ * @namespace
+ */
+jasmine.Clock = {
+ defaultFakeTimer: new jasmine.FakeTimer(),
+
+ reset: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.reset();
+ },
+
+ tick: function(millis) {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.tick(millis);
+ },
+
+ runFunctionsWithinRange: function(oldMillis, nowMillis) {
+ jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
+ },
+
+ scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
+ jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
+ },
+
+ useMock: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.after(jasmine.Clock.uninstallMock);
+
+ jasmine.Clock.installMock();
+ }
+ },
+
+ installMock: function() {
+ jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
+ },
+
+ uninstallMock: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.installed = jasmine.Clock.real;
+ },
+
+ real: {
+ setTimeout: jasmine.getGlobal().setTimeout,
+ clearTimeout: jasmine.getGlobal().clearTimeout,
+ setInterval: jasmine.getGlobal().setInterval,
+ clearInterval: jasmine.getGlobal().clearInterval
+ },
+
+ assertInstalled: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
+ }
+ },
+
+ isInstalled: function() {
+ return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
+ },
+
+ installed: null
+};
+jasmine.Clock.installed = jasmine.Clock.real;
+
+//else for IE support
+jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setTimeout.apply) {
+ return jasmine.Clock.installed.setTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setTimeout(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().setInterval = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setInterval.apply) {
+ return jasmine.Clock.installed.setInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setInterval(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().clearTimeout = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearTimeout(timeoutKey);
+ }
+};
+
+jasmine.getGlobal().clearInterval = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearInterval(timeoutKey);
+ }
+};
+
+/**
+ * @constructor
+ */
+jasmine.MultiReporter = function() {
+ this.subReporters_ = [];
+};
+jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
+
+jasmine.MultiReporter.prototype.addReporter = function(reporter) {
+ this.subReporters_.push(reporter);
+};
+
+(function() {
+ var functionNames = [
+ "reportRunnerStarting",
+ "reportRunnerResults",
+ "reportSuiteResults",
+ "reportSpecStarting",
+ "reportSpecResults",
+ "log"
+ ];
+ for (var i = 0; i < functionNames.length; i++) {
+ var functionName = functionNames[i];
+ jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
+ return function() {
+ for (var j = 0; j < this.subReporters_.length; j++) {
+ var subReporter = this.subReporters_[j];
+ if (subReporter[functionName]) {
+ subReporter[functionName].apply(subReporter, arguments);
+ }
+ }
+ };
+ })(functionName);
+ }
+})();
+/**
+ * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
+ *
+ * @constructor
+ */
+jasmine.NestedResults = function() {
+ /**
+ * The total count of results
+ */
+ this.totalCount = 0;
+ /**
+ * Number of passed results
+ */
+ this.passedCount = 0;
+ /**
+ * Number of failed results
+ */
+ this.failedCount = 0;
+ /**
+ * Was this suite/spec skipped?
+ */
+ this.skipped = false;
+ /**
+ * @ignore
+ */
+ this.items_ = [];
+};
+
+/**
+ * Roll up the result counts.
+ *
+ * @param result
+ */
+jasmine.NestedResults.prototype.rollupCounts = function(result) {
+ this.totalCount += result.totalCount;
+ this.passedCount += result.passedCount;
+ this.failedCount += result.failedCount;
+};
+
+/**
+ * Adds a log message.
+ * @param values Array of message parts which will be concatenated later.
+ */
+jasmine.NestedResults.prototype.log = function(values) {
+ this.items_.push(new jasmine.MessageResult(values));
+};
+
+/**
+ * Getter for the results: message & results.
+ */
+jasmine.NestedResults.prototype.getItems = function() {
+ return this.items_;
+};
+
+/**
+ * Adds a result, tracking counts (total, passed, & failed)
+ * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
+ */
+jasmine.NestedResults.prototype.addResult = function(result) {
+ if (result.type != 'log') {
+ if (result.items_) {
+ this.rollupCounts(result);
+ } else {
+ this.totalCount++;
+ if (result.passed()) {
+ this.passedCount++;
+ } else {
+ this.failedCount++;
+ }
+ }
+ }
+ this.items_.push(result);
+};
+
+/**
+ * @returns {Boolean} True if <b>everything</b> below passed
+ */
+jasmine.NestedResults.prototype.passed = function() {
+ return this.passedCount === this.totalCount;
+};
+/**
+ * Base class for pretty printing for expectation results.
+ */
+jasmine.PrettyPrinter = function() {
+ this.ppNestLevel_ = 0;
+};
+
+/**
+ * Formats a value in a nice, human-readable string.
+ *
+ * @param value
+ */
+jasmine.PrettyPrinter.prototype.format = function(value) {
+ this.ppNestLevel_++;
+ try {
+ if (value === jasmine.undefined) {
+ this.emitScalar('undefined');
+ } else if (value === null) {
+ this.emitScalar('null');
+ } else if (value === jasmine.getGlobal()) {
+ this.emitScalar('<global>');
+ } else if (value.jasmineToString) {
+ this.emitScalar(value.jasmineToString());
+ } else if (typeof value === 'string') {
+ this.emitString(value);
+ } else if (jasmine.isSpy(value)) {
+ this.emitScalar("spy on " + value.identity);
+ } else if (value instanceof RegExp) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'function') {
+ this.emitScalar('Function');
+ } else if (typeof value.nodeType === 'number') {
+ this.emitScalar('HTMLNode');
+ } else if (value instanceof Date) {
+ this.emitScalar('Date(' + value + ')');
+ } else if (value.__Jasmine_been_here_before__) {
+ this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
+ } else if (jasmine.isArray_(value) || typeof value == 'object') {
+ value.__Jasmine_been_here_before__ = true;
+ if (jasmine.isArray_(value)) {
+ this.emitArray(value);
+ } else {
+ this.emitObject(value);
+ }
+ delete value.__Jasmine_been_here_before__;
+ } else {
+ this.emitScalar(value.toString());
+ }
+ } finally {
+ this.ppNestLevel_--;
+ }
+};
+
+jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+ for (var property in obj) {
+ if (!obj.hasOwnProperty(property)) continue;
+ if (property == '__Jasmine_been_here_before__') continue;
+ fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined &&
+ obj.__lookupGetter__(property) !== null) : false);
+ }
+};
+
+jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
+
+jasmine.StringPrettyPrinter = function() {
+ jasmine.PrettyPrinter.call(this);
+
+ this.string = '';
+};
+jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
+
+jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
+ this.append(value);
+};
+
+jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
+ this.append("'" + value + "'");
+};
+
+jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
+ if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) {
+ this.append("Array");
+ return;
+ }
+
+ this.append('[ ');
+ for (var i = 0; i < array.length; i++) {
+ if (i > 0) {
+ this.append(', ');
+ }
+ this.format(array[i]);
+ }
+ this.append(' ]');
+};
+
+jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
+ if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) {
+ this.append("Object");
+ return;
+ }
+
+ var self = this;
+ this.append('{ ');
+ var first = true;
+
+ this.iterateObject(obj, function(property, isGetter) {
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.append(property);
+ self.append(' : ');
+ if (isGetter) {
+ self.append('<getter>');
+ } else {
+ self.format(obj[property]);
+ }
+ });
+
+ this.append(' }');
+};
+
+jasmine.StringPrettyPrinter.prototype.append = function(value) {
+ this.string += value;
+};
+jasmine.Queue = function(env) {
+ this.env = env;
+
+ // parallel to blocks. each true value in this array means the block will
+ // get executed even if we abort
+ this.ensured = [];
+ this.blocks = [];
+ this.running = false;
+ this.index = 0;
+ this.offset = 0;
+ this.abort = false;
+};
+
+jasmine.Queue.prototype.addBefore = function(block, ensure) {
+ if (ensure === jasmine.undefined) {
+ ensure = false;
+ }
+
+ this.blocks.unshift(block);
+ this.ensured.unshift(ensure);
+};
+
+jasmine.Queue.prototype.add = function(block, ensure) {
+ if (ensure === jasmine.undefined) {
+ ensure = false;
+ }
+
+ this.blocks.push(block);
+ this.ensured.push(ensure);
+};
+
+jasmine.Queue.prototype.insertNext = function(block, ensure) {
+ if (ensure === jasmine.undefined) {
+ ensure = false;
+ }
+
+ this.ensured.splice((this.index + this.offset + 1), 0, ensure);
+ this.blocks.splice((this.index + this.offset + 1), 0, block);
+ this.offset++;
+};
+
+jasmine.Queue.prototype.start = function(onComplete) {
+ this.running = true;
+ this.onComplete = onComplete;
+ this.next_();
+};
+
+jasmine.Queue.prototype.isRunning = function() {
+ return this.running;
+};
+
+jasmine.Queue.LOOP_DONT_RECURSE = true;
+
+jasmine.Queue.prototype.next_ = function() {
+ var self = this;
+ var goAgain = true;
+
+ while (goAgain) {
+ goAgain = false;
+
+ if (self.index < self.blocks.length && !(this.abort && !this.ensured[self.index])) {
+ var calledSynchronously = true;
+ var completedSynchronously = false;
+
+ var onComplete = function () {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
+ completedSynchronously = true;
+ return;
+ }
+
+ if (self.blocks[self.index].abort) {
+ self.abort = true;
+ }
+
+ self.offset = 0;
+ self.index++;
+
+ var now = new Date().getTime();
+ if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
+ self.env.lastUpdate = now;
+ self.env.setTimeout(function() {
+ self.next_();
+ }, 0);
+ } else {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
+ goAgain = true;
+ } else {
+ self.next_();
+ }
+ }
+ };
+ self.blocks[self.index].execute(onComplete);
+
+ calledSynchronously = false;
+ if (completedSynchronously) {
+ onComplete();
+ }
+
+ } else {
+ self.running = false;
+ if (self.onComplete) {
+ self.onComplete();
+ }
+ }
+ }
+};
+
+jasmine.Queue.prototype.results = function() {
+ var results = new jasmine.NestedResults();
+ for (var i = 0; i < this.blocks.length; i++) {
+ if (this.blocks[i].results) {
+ results.addResult(this.blocks[i].results());
+ }
+ }
+ return results;
+};
+
+
+/**
+ * Runner
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ */
+jasmine.Runner = function(env) {
+ var self = this;
+ self.env = env;
+ self.queue = new jasmine.Queue(env);
+ self.before_ = [];
+ self.after_ = [];
+ self.suites_ = [];
+};
+
+jasmine.Runner.prototype.execute = function() {
+ var self = this;
+ if (self.env.reporter.reportRunnerStarting) {
+ self.env.reporter.reportRunnerStarting(this);
+ }
+ self.queue.start(function () {
+ self.finishCallback();
+ });
+};
+
+jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.splice(0,0,beforeEachFunction);
+};
+
+jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.splice(0,0,afterEachFunction);
+};
+
+
+jasmine.Runner.prototype.finishCallback = function() {
+ this.env.reporter.reportRunnerResults(this);
+};
+
+jasmine.Runner.prototype.addSuite = function(suite) {
+ this.suites_.push(suite);
+};
+
+jasmine.Runner.prototype.add = function(block) {
+ if (block instanceof jasmine.Suite) {
+ this.addSuite(block);
+ }
+ this.queue.add(block);
+};
+
+jasmine.Runner.prototype.specs = function () {
+ var suites = this.suites();
+ var specs = [];
+ for (var i = 0; i < suites.length; i++) {
+ specs = specs.concat(suites[i].specs());
+ }
+ return specs;
+};
+
+jasmine.Runner.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Runner.prototype.topLevelSuites = function() {
+ var topLevelSuites = [];
+ for (var i = 0; i < this.suites_.length; i++) {
+ if (!this.suites_[i].parentSuite) {
+ topLevelSuites.push(this.suites_[i]);
+ }
+ }
+ return topLevelSuites;
+};
+
+jasmine.Runner.prototype.results = function() {
+ return this.queue.results();
+};
+/**
+ * Internal representation of a Jasmine specification, or test.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {jasmine.Suite} suite
+ * @param {String} description
+ */
+jasmine.Spec = function(env, suite, description) {
+ if (!env) {
+ throw new Error('jasmine.Env() required');
+ }
+ if (!suite) {
+ throw new Error('jasmine.Suite() required');
+ }
+ var spec = this;
+ spec.id = env.nextSpecId ? env.nextSpecId() : null;
+ spec.env = env;
+ spec.suite = suite;
+ spec.description = description;
+ spec.queue = new jasmine.Queue(env);
+
+ spec.afterCallbacks = [];
+ spec.spies_ = [];
+
+ spec.results_ = new jasmine.NestedResults();
+ spec.results_.description = description;
+ spec.matchersClass = null;
+};
+
+jasmine.Spec.prototype.getFullName = function() {
+ return this.suite.getFullName() + ' ' + this.description + '.';
+};
+
+
+jasmine.Spec.prototype.results = function() {
+ return this.results_;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the spec's output.
+ *
+ * Be careful not to leave calls to <code>jasmine.log</code> in production code.
+ */
+jasmine.Spec.prototype.log = function() {
+ return this.results_.log(arguments);
+};
+
+jasmine.Spec.prototype.runs = function (func) {
+ var block = new jasmine.Block(this.env, func, this);
+ this.addToQueue(block);
+ return this;
+};
+
+jasmine.Spec.prototype.addToQueue = function (block) {
+ if (this.queue.isRunning()) {
+ this.queue.insertNext(block);
+ } else {
+ this.queue.add(block);
+ }
+};
+
+/**
+ * @param {jasmine.ExpectationResult} result
+ */
+jasmine.Spec.prototype.addMatcherResult = function(result) {
+ this.results_.addResult(result);
+};
+
+jasmine.Spec.prototype.expect = function(actual) {
+ var positive = new (this.getMatchersClass_())(this.env, actual, this);
+ positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
+ return positive;
+};
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+jasmine.Spec.prototype.waits = function(timeout) {
+ var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
+ this.addToQueue(waitsFunc);
+ return this;
+};
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ var latchFunction_ = null;
+ var optional_timeoutMessage_ = null;
+ var optional_timeout_ = null;
+
+ for (var i = 0; i < arguments.length; i++) {
+ var arg = arguments[i];
+ switch (typeof arg) {
+ case 'function':
+ latchFunction_ = arg;
+ break;
+ case 'string':
+ optional_timeoutMessage_ = arg;
+ break;
+ case 'number':
+ optional_timeout_ = arg;
+ break;
+ }
+ }
+
+ var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
+ this.addToQueue(waitsForFunc);
+ return this;
+};
+
+jasmine.Spec.prototype.fail = function (e) {
+ var expectationResult = new jasmine.ExpectationResult({
+ passed: false,
+ message: e ? jasmine.util.formatException(e) : 'Exception',
+ trace: { stack: e.stack }
+ });
+ this.results_.addResult(expectationResult);
+};
+
+jasmine.Spec.prototype.getMatchersClass_ = function() {
+ return this.matchersClass || this.env.matchersClass;
+};
+
+jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
+ var parent = this.getMatchersClass_();
+ var newMatchersClass = function() {
+ parent.apply(this, arguments);
+ };
+ jasmine.util.inherit(newMatchersClass, parent);
+ jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
+ this.matchersClass = newMatchersClass;
+};
+
+jasmine.Spec.prototype.finishCallback = function() {
+ this.env.reporter.reportSpecResults(this);
+};
+
+jasmine.Spec.prototype.finish = function(onComplete) {
+ this.removeAllSpies();
+ this.finishCallback();
+ if (onComplete) {
+ onComplete();
+ }
+};
+
+jasmine.Spec.prototype.after = function(doAfter) {
+ if (this.queue.isRunning()) {
+ this.queue.add(new jasmine.Block(this.env, doAfter, this), true);
+ } else {
+ this.afterCallbacks.unshift(doAfter);
+ }
+};
+
+jasmine.Spec.prototype.execute = function(onComplete) {
+ var spec = this;
+ if (!spec.env.specFilter(spec)) {
+ spec.results_.skipped = true;
+ spec.finish(onComplete);
+ return;
+ }
+
+ this.env.reporter.reportSpecStarting(this);
+
+ spec.env.currentSpec = spec;
+
+ spec.addBeforesAndAftersToQueue();
+
+ spec.queue.start(function () {
+ spec.finish(onComplete);
+ });
+};
+
+jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
+ var runner = this.env.currentRunner();
+ var i;
+
+ for (var suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
+ }
+ }
+ for (i = 0; i < runner.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
+ }
+ for (i = 0; i < this.afterCallbacks.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this), true);
+ }
+ for (suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, suite.after_[i], this), true);
+ }
+ }
+ for (i = 0; i < runner.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, runner.after_[i], this), true);
+ }
+};
+
+jasmine.Spec.prototype.explodes = function() {
+ throw 'explodes function should not have been called';
+};
+
+jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
+ if (obj == jasmine.undefined) {
+ throw "spyOn could not find an object to spy upon for " + methodName + "()";
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
+ throw methodName + '() method does not exist';
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
+ throw new Error(methodName + ' has already been spied upon');
+ }
+
+ var spyObj = jasmine.createSpy(methodName);
+
+ this.spies_.push(spyObj);
+ spyObj.baseObj = obj;
+ spyObj.methodName = methodName;
+ spyObj.originalValue = obj[methodName];
+
+ obj[methodName] = spyObj;
+
+ return spyObj;
+};
+
+jasmine.Spec.prototype.removeAllSpies = function() {
+ for (var i = 0; i < this.spies_.length; i++) {
+ var spy = this.spies_[i];
+ spy.baseObj[spy.methodName] = spy.originalValue;
+ }
+ this.spies_ = [];
+};
+
+/**
+ * Internal representation of a Jasmine suite.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {String} description
+ * @param {Function} specDefinitions
+ * @param {jasmine.Suite} parentSuite
+ */
+jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
+ var self = this;
+ self.id = env.nextSuiteId ? env.nextSuiteId() : null;
+ self.description = description;
+ self.queue = new jasmine.Queue(env);
+ self.parentSuite = parentSuite;
+ self.env = env;
+ self.before_ = [];
+ self.after_ = [];
+ self.children_ = [];
+ self.suites_ = [];
+ self.specs_ = [];
+};
+
+jasmine.Suite.prototype.getFullName = function() {
+ var fullName = this.description;
+ for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+ fullName = parentSuite.description + ' ' + fullName;
+ }
+ return fullName;
+};
+
+jasmine.Suite.prototype.finish = function(onComplete) {
+ this.env.reporter.reportSuiteResults(this);
+ this.finished = true;
+ if (typeof(onComplete) == 'function') {
+ onComplete();
+ }
+};
+
+jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.unshift(beforeEachFunction);
+};
+
+jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.unshift(afterEachFunction);
+};
+
+jasmine.Suite.prototype.results = function() {
+ return this.queue.results();
+};
+
+jasmine.Suite.prototype.add = function(suiteOrSpec) {
+ this.children_.push(suiteOrSpec);
+ if (suiteOrSpec instanceof jasmine.Suite) {
+ this.suites_.push(suiteOrSpec);
+ this.env.currentRunner().addSuite(suiteOrSpec);
+ } else {
+ this.specs_.push(suiteOrSpec);
+ }
+ this.queue.add(suiteOrSpec);
+};
+
+jasmine.Suite.prototype.specs = function() {
+ return this.specs_;
+};
+
+jasmine.Suite.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Suite.prototype.children = function() {
+ return this.children_;
+};
+
+jasmine.Suite.prototype.execute = function(onComplete) {
+ var self = this;
+ this.queue.start(function () {
+ self.finish(onComplete);
+ });
+};
+jasmine.WaitsBlock = function(env, timeout, spec) {
+ this.timeout = timeout;
+ jasmine.Block.call(this, env, null, spec);
+};
+
+jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
+
+jasmine.WaitsBlock.prototype.execute = function (onComplete) {
+ if (jasmine.VERBOSE) {
+ this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
+ }
+ this.env.setTimeout(function () {
+ onComplete();
+ }, this.timeout);
+};
+/**
+ * A block which waits for some condition to become true, with timeout.
+ *
+ * @constructor
+ * @extends jasmine.Block
+ * @param {jasmine.Env} env The Jasmine environment.
+ * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
+ * @param {Function} latchFunction A function which returns true when the desired condition has been met.
+ * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
+ * @param {jasmine.Spec} spec The Jasmine spec.
+ */
+jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
+ this.timeout = timeout || env.defaultTimeoutInterval;
+ this.latchFunction = latchFunction;
+ this.message = message;
+ this.totalTimeSpentWaitingForLatch = 0;
+ jasmine.Block.call(this, env, null, spec);
+};
+jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
+
+jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
+
+jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
+ if (jasmine.VERBOSE) {
+ this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
+ }
+ var latchFunctionResult;
+ try {
+ latchFunctionResult = this.latchFunction.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ onComplete();
+ return;
+ }
+
+ if (latchFunctionResult) {
+ onComplete();
+ } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
+ var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
+ this.spec.fail({
+ name: 'timeout',
+ message: message
+ });
+
+ this.abort = true;
+ onComplete();
+ } else {
+ this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
+ var self = this;
+ this.env.setTimeout(function() {
+ self.execute(onComplete);
+ }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
+ }
+};
+
+jasmine.version_= {
+ "major": 1,
+ "minor": 3,
+ "build": 1,
+ "revision": 1354556913
+};
--- /dev/null
+/*!
+ * LESS - Leaner CSS v1.5.0
+ * http://lesscss.org
+ *
+ * Copyright (c) 2009-2013, Alexis Sellier <self@cloudhead.net>
+ * Licensed under the Apache v2 License.
+ *
+ * @licence
+ */
+
+
+
+(function (window, undefined) {//
+// Stub out `require` in the browser
+//
+function require(arg) {
+ return window.less[arg.split('/')[1]];
+};
+
+
+if (typeof(window.less) === 'undefined' || typeof(window.less.nodeType) !== 'undefined') { window.less = {}; }
+less = window.less;
+tree = window.less.tree = {};
+less.mode = 'browser';
+
+var less, tree;
+
+// Node.js does not have a header file added which defines less
+if (less === undefined) {
+ less = exports;
+ tree = require('./tree');
+ less.mode = 'node';
+}
+//
+// less.js - parser
+//
+// A relatively straight-forward predictive parser.
+// There is no tokenization/lexing stage, the input is parsed
+// in one sweep.
+//
+// To make the parser fast enough to run in the browser, several
+// optimization had to be made:
+//
+// - Matching and slicing on a huge input is often cause of slowdowns.
+// The solution is to chunkify the input into smaller strings.
+// The chunks are stored in the `chunks` var,
+// `j` holds the current chunk index, and `current` holds
+// the index of the current chunk in relation to `input`.
+// This gives us an almost 4x speed-up.
+//
+// - In many cases, we don't need to match individual tokens;
+// for example, if a value doesn't hold any variables, operations
+// or dynamic references, the parser can effectively 'skip' it,
+// treating it as a literal.
+// An example would be '1px solid #000' - which evaluates to itself,
+// we don't need to know what the individual components are.
+// The drawback, of course is that you don't get the benefits of
+// syntax-checking on the CSS. This gives us a 50% speed-up in the parser,
+// and a smaller speed-up in the code-gen.
+//
+//
+// Token matching is done with the `$` function, which either takes
+// a terminal string or regexp, or a non-terminal function to call.
+// It also takes care of moving all the indices forwards.
+//
+//
+less.Parser = function Parser(env) {
+ var input, // LeSS input string
+ i, // current index in `input`
+ j, // current chunk
+ temp, // temporarily holds a chunk's state, for backtracking
+ memo, // temporarily holds `i`, when backtracking
+ furthest, // furthest index the parser has gone to
+ chunks, // chunkified input
+ current, // index of current chunk, in `input`
+ parser,
+ rootFilename = env && env.filename;
+
+ // Top parser on an import tree must be sure there is one "env"
+ // which will then be passed around by reference.
+ if (!(env instanceof tree.parseEnv)) {
+ env = new tree.parseEnv(env);
+ }
+
+ var imports = this.imports = {
+ paths: env.paths || [], // Search paths, when importing
+ queue: [], // Files which haven't been imported yet
+ files: env.files, // Holds the imported parse trees
+ contents: env.contents, // Holds the imported file contents
+ mime: env.mime, // MIME type of .less files
+ error: null, // Error in parsing/evaluating an import
+ push: function (path, currentFileInfo, importOptions, callback) {
+ var parserImports = this;
+ this.queue.push(path);
+
+ var fileParsedFunc = function (e, root, fullPath) {
+ parserImports.queue.splice(parserImports.queue.indexOf(path), 1); // Remove the path from the queue
+
+ var importedPreviously = fullPath in parserImports.files || fullPath === rootFilename;
+
+ parserImports.files[fullPath] = root; // Store the root
+
+ if (e && !parserImports.error) { parserImports.error = e; }
+
+ callback(e, root, importedPreviously, fullPath);
+ };
+
+ if (less.Parser.importer) {
+ less.Parser.importer(path, currentFileInfo, fileParsedFunc, env);
+ } else {
+ less.Parser.fileLoader(path, currentFileInfo, function(e, contents, fullPath, newFileInfo) {
+ if (e) {fileParsedFunc(e); return;}
+
+ var newEnv = new tree.parseEnv(env);
+
+ newEnv.currentFileInfo = newFileInfo;
+ newEnv.processImports = false;
+ newEnv.contents[fullPath] = contents;
+
+ if (currentFileInfo.reference || importOptions.reference) {
+ newFileInfo.reference = true;
+ }
+
+ if (importOptions.inline) {
+ fileParsedFunc(null, contents, fullPath);
+ } else {
+ new(less.Parser)(newEnv).parse(contents, function (e, root) {
+ fileParsedFunc(e, root, fullPath);
+ });
+ }
+ }, env);
+ }
+ }
+ };
+
+ function save() { temp = chunks[j], memo = i, current = i; }
+ function restore() { chunks[j] = temp, i = memo, current = i; }
+
+ function sync() {
+ if (i > current) {
+ chunks[j] = chunks[j].slice(i - current);
+ current = i;
+ }
+ }
+ function isWhitespace(c) {
+ // Could change to \s?
+ var code = c.charCodeAt(0);
+ return code === 32 || code === 10 || code === 9;
+ }
+ //
+ // Parse from a token, regexp or string, and move forward if match
+ //
+ function $(tok) {
+ var match, length;
+
+ //
+ // Non-terminal
+ //
+ if (tok instanceof Function) {
+ return tok.call(parser.parsers);
+ //
+ // Terminal
+ //
+ // Either match a single character in the input,
+ // or match a regexp in the current chunk (chunk[j]).
+ //
+ } else if (typeof(tok) === 'string') {
+ match = input.charAt(i) === tok ? tok : null;
+ length = 1;
+ sync ();
+ } else {
+ sync ();
+
+ if (match = tok.exec(chunks[j])) {
+ length = match[0].length;
+ } else {
+ return null;
+ }
+ }
+
+ // The match is confirmed, add the match length to `i`,
+ // and consume any extra white-space characters (' ' || '\n')
+ // which come after that. The reason for this is that LeSS's
+ // grammar is mostly white-space insensitive.
+ //
+ if (match) {
+ skipWhitespace(length);
+
+ if(typeof(match) === 'string') {
+ return match;
+ } else {
+ return match.length === 1 ? match[0] : match;
+ }
+ }
+ }
+
+ function skipWhitespace(length) {
+ var oldi = i, oldj = j,
+ endIndex = i + chunks[j].length,
+ mem = i += length;
+
+ while (i < endIndex) {
+ if (! isWhitespace(input.charAt(i))) { break; }
+ i++;
+ }
+ chunks[j] = chunks[j].slice(length + (i - mem));
+ current = i;
+
+ if (chunks[j].length === 0 && j < chunks.length - 1) { j++; }
+
+ return oldi !== i || oldj !== j;
+ }
+
+ function expect(arg, msg) {
+ var result = $(arg);
+ if (! result) {
+ error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + input.charAt(i) + "'"
+ : "unexpected token"));
+ } else {
+ return result;
+ }
+ }
+
+ function error(msg, type) {
+ var e = new Error(msg);
+ e.index = i;
+ e.type = type || 'Syntax';
+ throw e;
+ }
+
+ // Same as $(), but don't change the state of the parser,
+ // just return the match.
+ function peek(tok) {
+ if (typeof(tok) === 'string') {
+ return input.charAt(i) === tok;
+ } else {
+ return tok.test(chunks[j]);
+ }
+ }
+
+ function getInput(e, env) {
+ if (e.filename && env.currentFileInfo.filename && (e.filename !== env.currentFileInfo.filename)) {
+ return parser.imports.contents[e.filename];
+ } else {
+ return input;
+ }
+ }
+
+ function getLocation(index, inputStream) {
+ var n = index + 1,
+ line = null,
+ column = -1;
+
+ while (--n >= 0 && inputStream.charAt(n) !== '\n') {
+ column++;
+ }
+
+ if (typeof index === 'number') {
+ line = (inputStream.slice(0, index).match(/\n/g) || "").length;
+ }
+
+ return {
+ line: line,
+ column: column
+ };
+ }
+
+ function getDebugInfo(index, inputStream, env) {
+ var filename = env.currentFileInfo.filename;
+ if(less.mode !== 'browser' && less.mode !== 'rhino') {
+ filename = require('path').resolve(filename);
+ }
+
+ return {
+ lineNumber: getLocation(index, inputStream).line + 1,
+ fileName: filename
+ };
+ }
+
+ function LessError(e, env) {
+ var input = getInput(e, env),
+ loc = getLocation(e.index, input),
+ line = loc.line,
+ col = loc.column,
+ callLine = e.call && getLocation(e.call, input).line,
+ lines = input.split('\n');
+
+ this.type = e.type || 'Syntax';
+ this.message = e.message;
+ this.filename = e.filename || env.currentFileInfo.filename;
+ this.index = e.index;
+ this.line = typeof(line) === 'number' ? line + 1 : null;
+ this.callLine = callLine + 1;
+ this.callExtract = lines[callLine];
+ this.stack = e.stack;
+ this.column = col;
+ this.extract = [
+ lines[line - 1],
+ lines[line],
+ lines[line + 1]
+ ];
+ }
+
+ LessError.prototype = new Error();
+ LessError.prototype.constructor = LessError;
+
+ this.env = env = env || {};
+
+ // The optimization level dictates the thoroughness of the parser,
+ // the lower the number, the less nodes it will create in the tree.
+ // This could matter for debugging, or if you want to access
+ // the individual nodes in the tree.
+ this.optimization = ('optimization' in this.env) ? this.env.optimization : 1;
+
+ //
+ // The Parser
+ //
+ return parser = {
+
+ imports: imports,
+ //
+ // Parse an input string into an abstract syntax tree,
+ // call `callback` when done.
+ //
+ parse: function (str, callback) {
+ var root, line, lines, error = null;
+
+ i = j = current = furthest = 0;
+ input = str.replace(/\r\n/g, '\n');
+
+ // Remove potential UTF Byte Order Mark
+ input = input.replace(/^\uFEFF/, '');
+
+ parser.imports.contents[env.currentFileInfo.filename] = input;
+
+ // Split the input into chunks.
+ chunks = (function (chunks) {
+ var j = 0,
+ skip = /(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g,
+ comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
+ string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g,
+ level = 0,
+ match,
+ chunk = chunks[0],
+ inParam;
+
+ for (var i = 0, c, cc; i < input.length;) {
+ skip.lastIndex = i;
+ if (match = skip.exec(input)) {
+ if (match.index === i) {
+ i += match[0].length;
+ chunk.push(match[0]);
+ }
+ }
+ c = input.charAt(i);
+ comment.lastIndex = string.lastIndex = i;
+
+ if (match = string.exec(input)) {
+ if (match.index === i) {
+ i += match[0].length;
+ chunk.push(match[0]);
+ continue;
+ }
+ }
+
+ if (!inParam && c === '/') {
+ cc = input.charAt(i + 1);
+ if (cc === '/' || cc === '*') {
+ if (match = comment.exec(input)) {
+ if (match.index === i) {
+ i += match[0].length;
+ chunk.push(match[0]);
+ continue;
+ }
+ }
+ }
+ }
+
+ switch (c) {
+ case '{':
+ if (!inParam) {
+ level++;
+ chunk.push(c);
+ break;
+ }
+ /* falls through */
+ case '}':
+ if (!inParam) {
+ level--;
+ chunk.push(c);
+ chunks[++j] = chunk = [];
+ break;
+ }
+ /* falls through */
+ case '(':
+ if (!inParam) {
+ inParam = true;
+ chunk.push(c);
+ break;
+ }
+ /* falls through */
+ case ')':
+ if (inParam) {
+ inParam = false;
+ chunk.push(c);
+ break;
+ }
+ /* falls through */
+ default:
+ chunk.push(c);
+ }
+
+ i++;
+ }
+ if (level !== 0) {
+ error = new(LessError)({
+ index: i-1,
+ type: 'Parse',
+ message: (level > 0) ? "missing closing `}`" : "missing opening `{`",
+ filename: env.currentFileInfo.filename
+ }, env);
+ }
+
+ return chunks.map(function (c) { return c.join(''); });
+ })([[]]);
+
+ if (error) {
+ return callback(new(LessError)(error, env));
+ }
+
+ // Start with the primary rule.
+ // The whole syntax tree is held under a Ruleset node,
+ // with the `root` property set to true, so no `{}` are
+ // output. The callback is called when the input is parsed.
+ try {
+ root = new(tree.Ruleset)([], $(this.parsers.primary));
+ root.root = true;
+ root.firstRoot = true;
+ } catch (e) {
+ return callback(new(LessError)(e, env));
+ }
+
+ root.toCSS = (function (evaluate) {
+ return function (options, variables) {
+ options = options || {};
+ var evaldRoot,
+ css,
+ evalEnv = new tree.evalEnv(options);
+
+ //
+ // Allows setting variables with a hash, so:
+ //
+ // `{ color: new(tree.Color)('#f01') }` will become:
+ //
+ // new(tree.Rule)('@color',
+ // new(tree.Value)([
+ // new(tree.Expression)([
+ // new(tree.Color)('#f01')
+ // ])
+ // ])
+ // )
+ //
+ if (typeof(variables) === 'object' && !Array.isArray(variables)) {
+ variables = Object.keys(variables).map(function (k) {
+ var value = variables[k];
+
+ if (! (value instanceof tree.Value)) {
+ if (! (value instanceof tree.Expression)) {
+ value = new(tree.Expression)([value]);
+ }
+ value = new(tree.Value)([value]);
+ }
+ return new(tree.Rule)('@' + k, value, false, null, 0);
+ });
+ evalEnv.frames = [new(tree.Ruleset)(null, variables)];
+ }
+
+ try {
+ evaldRoot = evaluate.call(this, evalEnv);
+
+ new(tree.joinSelectorVisitor)()
+ .run(evaldRoot);
+
+ new(tree.processExtendsVisitor)()
+ .run(evaldRoot);
+
+ new(tree.toCSSVisitor)({compress: Boolean(options.compress)})
+ .run(evaldRoot);
+
+ if (options.sourceMap) {
+ evaldRoot = new tree.sourceMapOutput(
+ {
+ writeSourceMap: options.writeSourceMap,
+ rootNode: evaldRoot,
+ contentsMap: parser.imports.contents,
+ sourceMapFilename: options.sourceMapFilename,
+ outputFilename: options.sourceMapOutputFilename,
+ sourceMapBasepath: options.sourceMapBasepath,
+ sourceMapRootpath: options.sourceMapRootpath,
+ outputSourceFiles: options.outputSourceFiles,
+ sourceMapGenerator: options.sourceMapGenerator
+ });
+ }
+
+ css = evaldRoot.toCSS({
+ compress: Boolean(options.compress),
+ dumpLineNumbers: env.dumpLineNumbers,
+ strictUnits: Boolean(options.strictUnits)});
+ } catch (e) {
+ throw new(LessError)(e, env);
+ }
+
+ if (options.cleancss && less.mode === 'node') {
+ var CleanCSS = require('clean-css');
+ //TODO would be nice for no advanced to be an option
+ return new CleanCSS({keepSpecialComments: '*', processImport: false, noRebase: true, noAdvanced: true}).minify(css);
+ } else if (options.compress) {
+ return css.replace(/(^(\s)+)|((\s)+$)/g, "");
+ } else {
+ return css;
+ }
+ };
+ })(root.eval);
+
+ // If `i` is smaller than the `input.length - 1`,
+ // it means the parser wasn't able to parse the whole
+ // string, so we've got a parsing error.
+ //
+ // We try to extract a \n delimited string,
+ // showing the line where the parse error occured.
+ // We split it up into two parts (the part which parsed,
+ // and the part which didn't), so we can color them differently.
+ if (i < input.length - 1) {
+ i = furthest;
+ var loc = getLocation(i, input);
+ lines = input.split('\n');
+ line = loc.line + 1;
+
+ error = {
+ type: "Parse",
+ message: "Unrecognised input",
+ index: i,
+ filename: env.currentFileInfo.filename,
+ line: line,
+ column: loc.column,
+ extract: [
+ lines[line - 2],
+ lines[line - 1],
+ lines[line]
+ ]
+ };
+ }
+
+ var finish = function (e) {
+ e = error || e || parser.imports.error;
+
+ if (e) {
+ if (!(e instanceof LessError)) {
+ e = new(LessError)(e, env);
+ }
+
+ return callback(e);
+ }
+ else {
+ return callback(null, root);
+ }
+ };
+
+ if (env.processImports !== false) {
+ new tree.importVisitor(this.imports, finish)
+ .run(root);
+ } else {
+ return finish();
+ }
+ },
+
+ //
+ // Here in, the parsing rules/functions
+ //
+ // The basic structure of the syntax tree generated is as follows:
+ //
+ // Ruleset -> Rule -> Value -> Expression -> Entity
+ //
+ // Here's some LESS code:
+ //
+ // .class {
+ // color: #fff;
+ // border: 1px solid #000;
+ // width: @w + 4px;
+ // > .child {...}
+ // }
+ //
+ // And here's what the parse tree might look like:
+ //
+ // Ruleset (Selector '.class', [
+ // Rule ("color", Value ([Expression [Color #fff]]))
+ // Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
+ // Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
+ // Ruleset (Selector [Element '>', '.child'], [...])
+ // ])
+ //
+ // In general, most rules will try to parse a token with the `$()` function, and if the return
+ // value is truly, will return a new node, of the relevant type. Sometimes, we need to check
+ // first, before parsing, that's when we use `peek()`.
+ //
+ parsers: {
+ //
+ // The `primary` rule is the *entry* and *exit* point of the parser.
+ // The rules here can appear at any level of the parse tree.
+ //
+ // The recursive nature of the grammar is an interplay between the `block`
+ // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
+ // as represented by this simplified grammar:
+ //
+ // primary → (ruleset | rule)+
+ // ruleset → selector+ block
+ // block → '{' primary '}'
+ //
+ // Only at one point is the primary rule not called from the
+ // block rule: at the root level.
+ //
+ primary: function () {
+ var node, root = [];
+
+ while ((node = $(this.extendRule) || $(this.mixin.definition) || $(this.rule) || $(this.ruleset) ||
+ $(this.mixin.call) || $(this.comment) || $(this.directive))
+ || $(/^[\s\n]+/) || $(/^;+/)) {
+ node && root.push(node);
+ }
+ return root;
+ },
+
+ // We create a Comment node for CSS comments `/* */`,
+ // but keep the LeSS comments `//` silent, by just skipping
+ // over them.
+ comment: function () {
+ var comment;
+
+ if (input.charAt(i) !== '/') { return; }
+
+ if (input.charAt(i + 1) === '/') {
+ return new(tree.Comment)($(/^\/\/.*/), true, i, env.currentFileInfo);
+ } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) {
+ return new(tree.Comment)(comment, false, i, env.currentFileInfo);
+ }
+ },
+
+ comments: function () {
+ var comment, comments = [];
+
+ while(comment = $(this.comment)) {
+ comments.push(comment);
+ }
+
+ return comments;
+ },
+
+ //
+ // Entities are tokens which can be found inside an Expression
+ //
+ entities: {
+ //
+ // A string, which supports escaping " and '
+ //
+ // "milky way" 'he\'s the one!'
+ //
+ quoted: function () {
+ var str, j = i, e, index = i;
+
+ if (input.charAt(j) === '~') { j++, e = true; } // Escaped strings
+ if (input.charAt(j) !== '"' && input.charAt(j) !== "'") { return; }
+
+ e && $('~');
+
+ if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) {
+ return new(tree.Quoted)(str[0], str[1] || str[2], e, index, env.currentFileInfo);
+ }
+ },
+
+ //
+ // A catch-all word, such as:
+ //
+ // black border-collapse
+ //
+ keyword: function () {
+ var k;
+
+ if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) {
+ var color = tree.Color.fromKeyword(k);
+ if (color) {
+ return color;
+ }
+ return new(tree.Keyword)(k);
+ }
+ },
+
+ //
+ // A function call
+ //
+ // rgb(255, 0, 255)
+ //
+ // We also try to catch IE's `alpha()`, but let the `alpha` parser
+ // deal with the details.
+ //
+ // The arguments are parsed with the `entities.arguments` parser.
+ //
+ call: function () {
+ var name, nameLC, args, alpha_ret, index = i;
+
+ if (! (name = /^([\w-]+|%|progid:[\w\.]+)\(/.exec(chunks[j]))) { return; }
+
+ name = name[1];
+ nameLC = name.toLowerCase();
+
+ if (nameLC === 'url') { return null; }
+ else { i += name.length; }
+
+ if (nameLC === 'alpha') {
+ alpha_ret = $(this.alpha);
+ if(typeof alpha_ret !== 'undefined') {
+ return alpha_ret;
+ }
+ }
+
+ $('('); // Parse the '(' and consume whitespace.
+
+ args = $(this.entities.arguments);
+
+ if (! $(')')) {
+ return;
+ }
+
+ if (name) { return new(tree.Call)(name, args, index, env.currentFileInfo); }
+ },
+ arguments: function () {
+ var args = [], arg;
+
+ while (arg = $(this.entities.assignment) || $(this.expression)) {
+ args.push(arg);
+ if (! $(',')) {
+ break;
+ }
+ }
+ return args;
+ },
+ literal: function () {
+ return $(this.entities.dimension) ||
+ $(this.entities.color) ||
+ $(this.entities.quoted) ||
+ $(this.entities.unicodeDescriptor);
+ },
+
+ // Assignments are argument entities for calls.
+ // They are present in ie filter properties as shown below.
+ //
+ // filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
+ //
+
+ assignment: function () {
+ var key, value;
+ if ((key = $(/^\w+(?=\s?=)/i)) && $('=') && (value = $(this.entity))) {
+ return new(tree.Assignment)(key, value);
+ }
+ },
+
+ //
+ // Parse url() tokens
+ //
+ // We use a specific rule for urls, because they don't really behave like
+ // standard function calls. The difference is that the argument doesn't have
+ // to be enclosed within a string, so it can't be parsed as an Expression.
+ //
+ url: function () {
+ var value;
+
+ if (input.charAt(i) !== 'u' || !$(/^url\(/)) {
+ return;
+ }
+
+ value = $(this.entities.quoted) || $(this.entities.variable) ||
+ $(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/) || "";
+
+ expect(')');
+
+ /*jshint eqnull:true */
+ return new(tree.URL)((value.value != null || value instanceof tree.Variable)
+ ? value : new(tree.Anonymous)(value), env.currentFileInfo);
+ },
+
+ //
+ // A Variable entity, such as `@fink`, in
+ //
+ // width: @fink + 2px
+ //
+ // We use a different parser for variable definitions,
+ // see `parsers.variable`.
+ //
+ variable: function () {
+ var name, index = i;
+
+ if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) {
+ return new(tree.Variable)(name, index, env.currentFileInfo);
+ }
+ },
+
+ // A variable entity useing the protective {} e.g. @{var}
+ variableCurly: function () {
+ var curly, index = i;
+
+ if (input.charAt(i) === '@' && (curly = $(/^@\{([\w-]+)\}/))) {
+ return new(tree.Variable)("@" + curly[1], index, env.currentFileInfo);
+ }
+ },
+
+ //
+ // A Hexadecimal color
+ //
+ // #4F3C2F
+ //
+ // `rgb` and `hsl` colors are parsed through the `entities.call` parser.
+ //
+ color: function () {
+ var rgb;
+
+ if (input.charAt(i) === '#' && (rgb = $(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/))) {
+ return new(tree.Color)(rgb[1]);
+ }
+ },
+
+ //
+ // A Dimension, that is, a number and a unit
+ //
+ // 0.5em 95%
+ //
+ dimension: function () {
+ var value, c = input.charCodeAt(i);
+ //Is the first char of the dimension 0-9, '.', '+' or '-'
+ if ((c > 57 || c < 43) || c === 47 || c == 44) {
+ return;
+ }
+
+ if (value = $(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/)) {
+ return new(tree.Dimension)(value[1], value[2]);
+ }
+ },
+
+ //
+ // A unicode descriptor, as is used in unicode-range
+ //
+ // U+0?? or U+00A1-00A9
+ //
+ unicodeDescriptor: function () {
+ var ud;
+
+ if (ud = $(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/)) {
+ return new(tree.UnicodeDescriptor)(ud[0]);
+ }
+ },
+
+ //
+ // JavaScript code to be evaluated
+ //
+ // `window.location.href`
+ //
+ javascript: function () {
+ var str, j = i, e;
+
+ if (input.charAt(j) === '~') { j++; e = true; } // Escaped strings
+ if (input.charAt(j) !== '`') { return; }
+ if (env.javascriptEnabled !== undefined && !env.javascriptEnabled) {
+ error("You are using JavaScript, which has been disabled.");
+ }
+
+ if (e) { $('~'); }
+
+ if (str = $(/^`([^`]*)`/)) {
+ return new(tree.JavaScript)(str[1], i, e);
+ }
+ }
+ },
+
+ //
+ // The variable part of a variable definition. Used in the `rule` parser
+ //
+ // @fink:
+ //
+ variable: function () {
+ var name;
+
+ if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1]; }
+ },
+
+ //
+ // extend syntax - used to extend selectors
+ //
+ extend: function(isRule) {
+ var elements, e, index = i, option, extendList = [];
+
+ if (!$(isRule ? /^&:extend\(/ : /^:extend\(/)) { return; }
+
+ do {
+ option = null;
+ elements = [];
+ while (true) {
+ option = $(/^(all)(?=\s*(\)|,))/);
+ if (option) { break; }
+ e = $(this.element);
+ if (!e) { break; }
+ elements.push(e);
+ }
+
+ option = option && option[1];
+
+ extendList.push(new(tree.Extend)(new(tree.Selector)(elements), option, index));
+
+ } while($(","));
+
+ expect(/^\)/);
+
+ if (isRule) {
+ expect(/^;/);
+ }
+
+ return extendList;
+ },
+
+ //
+ // extendRule - used in a rule to extend all the parent selectors
+ //
+ extendRule: function() {
+ return this.extend(true);
+ },
+
+ //
+ // Mixins
+ //
+ mixin: {
+ //
+ // A Mixin call, with an optional argument list
+ //
+ // #mixins > .square(#fff);
+ // .rounded(4px, black);
+ // .button;
+ //
+ // The `while` loop is there because mixins can be
+ // namespaced, but we only support the child and descendant
+ // selector for now.
+ //
+ call: function () {
+ var elements = [], e, c, args, index = i, s = input.charAt(i), important = false;
+
+ if (s !== '.' && s !== '#') { return; }
+
+ save(); // stop us absorbing part of an invalid selector
+
+ while (e = $(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)) {
+ elements.push(new(tree.Element)(c, e, i, env.currentFileInfo));
+ c = $('>');
+ }
+ if ($('(')) {
+ args = this.mixin.args.call(this, true).args;
+ expect(')');
+ }
+
+ args = args || [];
+
+ if ($(this.important)) {
+ important = true;
+ }
+
+ if (elements.length > 0 && ($(';') || peek('}'))) {
+ return new(tree.mixin.Call)(elements, args, index, env.currentFileInfo, important);
+ }
+
+ restore();
+ },
+ args: function (isCall) {
+ var expressions = [], argsSemiColon = [], isSemiColonSeperated, argsComma = [], expressionContainsNamed, name, nameLoop, value, arg,
+ returner = {args:null, variadic: false};
+ while (true) {
+ if (isCall) {
+ arg = $(this.expression);
+ } else {
+ $(this.comments);
+ if (input.charAt(i) === '.' && $(/^\.{3}/)) {
+ returner.variadic = true;
+ if ($(";") && !isSemiColonSeperated) {
+ isSemiColonSeperated = true;
+ }
+ (isSemiColonSeperated ? argsSemiColon : argsComma)
+ .push({ variadic: true });
+ break;
+ }
+ arg = $(this.entities.variable) || $(this.entities.literal)
+ || $(this.entities.keyword);
+ }
+
+ if (!arg) {
+ break;
+ }
+
+ nameLoop = null;
+ if (arg.throwAwayComments) {
+ arg.throwAwayComments();
+ }
+ value = arg;
+ var val = null;
+
+ if (isCall) {
+ // Variable
+ if (arg.value.length == 1) {
+ val = arg.value[0];
+ }
+ } else {
+ val = arg;
+ }
+
+ if (val && val instanceof tree.Variable) {
+ if ($(':')) {
+ if (expressions.length > 0) {
+ if (isSemiColonSeperated) {
+ error("Cannot mix ; and , as delimiter types");
+ }
+ expressionContainsNamed = true;
+ }
+ value = expect(this.expression);
+ nameLoop = (name = val.name);
+ } else if (!isCall && $(/^\.{3}/)) {
+ returner.variadic = true;
+ if ($(";") && !isSemiColonSeperated) {
+ isSemiColonSeperated = true;
+ }
+ (isSemiColonSeperated ? argsSemiColon : argsComma)
+ .push({ name: arg.name, variadic: true });
+ break;
+ } else if (!isCall) {
+ name = nameLoop = val.name;
+ value = null;
+ }
+ }
+
+ if (value) {
+ expressions.push(value);
+ }
+
+ argsComma.push({ name:nameLoop, value:value });
+
+ if ($(',')) {
+ continue;
+ }
+
+ if ($(';') || isSemiColonSeperated) {
+
+ if (expressionContainsNamed) {
+ error("Cannot mix ; and , as delimiter types");
+ }
+
+ isSemiColonSeperated = true;
+
+ if (expressions.length > 1) {
+ value = new(tree.Value)(expressions);
+ }
+ argsSemiColon.push({ name:name, value:value });
+
+ name = null;
+ expressions = [];
+ expressionContainsNamed = false;
+ }
+ }
+
+ returner.args = isSemiColonSeperated ? argsSemiColon : argsComma;
+ return returner;
+ },
+ //
+ // A Mixin definition, with a list of parameters
+ //
+ // .rounded (@radius: 2px, @color) {
+ // ...
+ // }
+ //
+ // Until we have a finer grained state-machine, we have to
+ // do a look-ahead, to make sure we don't have a mixin call.
+ // See the `rule` function for more information.
+ //
+ // We start by matching `.rounded (`, and then proceed on to
+ // the argument list, which has optional default values.
+ // We store the parameters in `params`, with a `value` key,
+ // if there is a value, such as in the case of `@radius`.
+ //
+ // Once we've got our params list, and a closing `)`, we parse
+ // the `{...}` block.
+ //
+ definition: function () {
+ var name, params = [], match, ruleset, cond, variadic = false;
+ if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') ||
+ peek(/^[^{]*\}/)) {
+ return;
+ }
+
+ save();
+
+ if (match = $(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)) {
+ name = match[1];
+
+ var argInfo = this.mixin.args.call(this, false);
+ params = argInfo.args;
+ variadic = argInfo.variadic;
+
+ // .mixincall("@{a}");
+ // looks a bit like a mixin definition.. so we have to be nice and restore
+ if (!$(')')) {
+ furthest = i;
+ restore();
+ }
+
+ $(this.comments);
+
+ if ($(/^when/)) { // Guard
+ cond = expect(this.conditions, 'expected condition');
+ }
+
+ ruleset = $(this.block);
+
+ if (ruleset) {
+ return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
+ } else {
+ restore();
+ }
+ }
+ }
+ },
+
+ //
+ // Entities are the smallest recognized token,
+ // and can be found inside a rule's value.
+ //
+ entity: function () {
+ return $(this.entities.literal) || $(this.entities.variable) || $(this.entities.url) ||
+ $(this.entities.call) || $(this.entities.keyword) ||$(this.entities.javascript) ||
+ $(this.comment);
+ },
+
+ //
+ // A Rule terminator. Note that we use `peek()` to check for '}',
+ // because the `block` rule will be expecting it, but we still need to make sure
+ // it's there, if ';' was ommitted.
+ //
+ end: function () {
+ return $(';') || peek('}');
+ },
+
+ //
+ // IE's alpha function
+ //
+ // alpha(opacity=88)
+ //
+ alpha: function () {
+ var value;
+
+ if (! $(/^\(opacity=/i)) { return; }
+ if (value = $(/^\d+/) || $(this.entities.variable)) {
+ expect(')');
+ return new(tree.Alpha)(value);
+ }
+ },
+
+ //
+ // A Selector Element
+ //
+ // div
+ // + h1
+ // #socks
+ // input[type="text"]
+ //
+ // Elements are the building blocks for Selectors,
+ // they are made out of a `Combinator` (see combinator rule),
+ // and an element name, such as a tag a class, or `*`.
+ //
+ element: function () {
+ var e, c, v;
+
+ c = $(this.combinator);
+
+ e = $(/^(?:\d+\.\d+|\d+)%/) || $(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) ||
+ $('*') || $('&') || $(this.attribute) || $(/^\([^()@]+\)/) || $(/^[\.#](?=@)/) || $(this.entities.variableCurly);
+
+ if (! e) {
+ if ($('(')) {
+ if ((v = ($(this.selector))) &&
+ $(')')) {
+ e = new(tree.Paren)(v);
+ }
+ }
+ }
+
+ if (e) { return new(tree.Element)(c, e, i, env.currentFileInfo); }
+ },
+
+ //
+ // Combinators combine elements together, in a Selector.
+ //
+ // Because our parser isn't white-space sensitive, special care
+ // has to be taken, when parsing the descendant combinator, ` `,
+ // as it's an empty space. We have to check the previous character
+ // in the input, to see if it's a ` ` character. More info on how
+ // we deal with this in *combinator.js*.
+ //
+ combinator: function () {
+ var c = input.charAt(i);
+
+ if (c === '>' || c === '+' || c === '~' || c === '|') {
+ i++;
+ while (input.charAt(i).match(/\s/)) { i++; }
+ return new(tree.Combinator)(c);
+ } else if (input.charAt(i - 1).match(/\s/)) {
+ return new(tree.Combinator)(" ");
+ } else {
+ return new(tree.Combinator)(null);
+ }
+ },
+ //
+ // A CSS selector (see selector below)
+ // with less extensions e.g. the ability to extend and guard
+ //
+ lessSelector: function () {
+ return this.selector(true);
+ },
+ //
+ // A CSS Selector
+ //
+ // .class > div + h1
+ // li a:hover
+ //
+ // Selectors are made out of one or more Elements, see above.
+ //
+ selector: function (isLess) {
+ var e, elements = [], c, extend, extendList = [], when, condition;
+
+ while ((isLess && (extend = $(this.extend))) || (isLess && (when = $(/^when/))) || (e = $(this.element))) {
+ if (when) {
+ condition = expect(this.conditions, 'expected condition');
+ } else if (condition) {
+ error("CSS guard can only be used at the end of selector");
+ } else if (extend) {
+ extendList.push.apply(extendList, extend);
+ } else {
+ if (extendList.length) {
+ error("Extend can only be used at the end of selector");
+ }
+ c = input.charAt(i);
+ elements.push(e);
+ e = null;
+ }
+ if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') {
+ break;
+ }
+ }
+
+ if (elements.length > 0) { return new(tree.Selector)(elements, extendList, condition, i, env.currentFileInfo); }
+ if (extendList.length) { error("Extend must be used to extend a selector, it cannot be used on its own"); }
+ },
+ attribute: function () {
+ var key, val, op;
+
+ if (! $('[')) { return; }
+
+ if (!(key = $(this.entities.variableCurly))) {
+ key = expect(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/);
+ }
+
+ if ((op = $(/^[|~*$^]?=/))) {
+ val = $(this.entities.quoted) || $(/^[0-9]+%/) || $(/^[\w-]+/) || $(this.entities.variableCurly);
+ }
+
+ expect(']');
+
+ return new(tree.Attribute)(key, op, val);
+ },
+
+ //
+ // The `block` rule is used by `ruleset` and `mixin.definition`.
+ // It's a wrapper around the `primary` rule, with added `{}`.
+ //
+ block: function () {
+ var content;
+ if ($('{') && (content = $(this.primary)) && $('}')) {
+ return content;
+ }
+ },
+
+ //
+ // div, .class, body > p {...}
+ //
+ ruleset: function () {
+ var selectors = [], s, rules, debugInfo;
+
+ save();
+
+ if (env.dumpLineNumbers) {
+ debugInfo = getDebugInfo(i, input, env);
+ }
+
+ while (s = $(this.lessSelector)) {
+ selectors.push(s);
+ $(this.comments);
+ if (! $(',')) { break; }
+ if (s.condition) {
+ error("Guards are only currently allowed on a single selector.");
+ }
+ $(this.comments);
+ }
+
+ if (selectors.length > 0 && (rules = $(this.block))) {
+ var ruleset = new(tree.Ruleset)(selectors, rules, env.strictImports);
+ if (env.dumpLineNumbers) {
+ ruleset.debugInfo = debugInfo;
+ }
+ return ruleset;
+ } else {
+ // Backtrack
+ furthest = i;
+ restore();
+ }
+ },
+ rule: function (tryAnonymous) {
+ var name, value, c = input.charAt(i), important, merge = false;
+ save();
+
+ if (c === '.' || c === '#' || c === '&') { return; }
+
+ if (name = $(this.variable) || $(this.ruleProperty)) {
+ // prefer to try to parse first if its a variable or we are compressing
+ // but always fallback on the other one
+ value = !tryAnonymous && (env.compress || (name.charAt(0) === '@')) ?
+ ($(this.value) || $(this.anonymousValue)) :
+ ($(this.anonymousValue) || $(this.value));
+
+
+ important = $(this.important);
+ if (name[name.length-1] === "+") {
+ merge = true;
+ name = name.substr(0, name.length - 1);
+ }
+
+ if (value && $(this.end)) {
+ return new (tree.Rule)(name, value, important, merge, memo, env.currentFileInfo);
+ } else {
+ furthest = i;
+ restore();
+ if (value && !tryAnonymous) {
+ return this.rule(true);
+ }
+ }
+ }
+ },
+ anonymousValue: function () {
+ var match;
+ if (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j])) {
+ i += match[0].length - 1;
+ return new(tree.Anonymous)(match[1]);
+ }
+ },
+
+ //
+ // An @import directive
+ //
+ // @import "lib";
+ //
+ // Depending on our environemnt, importing is done differently:
+ // In the browser, it's an XHR request, in Node, it would be a
+ // file-system operation. The function used for importing is
+ // stored in `import`, which we pass to the Import constructor.
+ //
+ "import": function () {
+ var path, features, index = i;
+
+ save();
+
+ var dir = $(/^@import?\s+/);
+
+ var options = (dir ? $(this.importOptions) : null) || {};
+
+ if (dir && (path = $(this.entities.quoted) || $(this.entities.url))) {
+ features = $(this.mediaFeatures);
+ if ($(';')) {
+ features = features && new(tree.Value)(features);
+ return new(tree.Import)(path, features, options, index, env.currentFileInfo);
+ }
+ }
+
+ restore();
+ },
+
+ importOptions: function() {
+ var o, options = {}, optionName, value;
+
+ // list of options, surrounded by parens
+ if (! $('(')) { return null; }
+ do {
+ if (o = $(this.importOption)) {
+ optionName = o;
+ value = true;
+ switch(optionName) {
+ case "css":
+ optionName = "less";
+ value = false;
+ break;
+ case "once":
+ optionName = "multiple";
+ value = false;
+ break;
+ }
+ options[optionName] = value;
+ if (! $(',')) { break; }
+ }
+ } while (o);
+ expect(')');
+ return options;
+ },
+
+ importOption: function() {
+ var opt = $(/^(less|css|multiple|once|inline|reference)/);
+ if (opt) {
+ return opt[1];
+ }
+ },
+
+ mediaFeature: function () {
+ var e, p, nodes = [];
+
+ do {
+ if (e = ($(this.entities.keyword) || $(this.entities.variable))) {
+ nodes.push(e);
+ } else if ($('(')) {
+ p = $(this.property);
+ e = $(this.value);
+ if ($(')')) {
+ if (p && e) {
+ nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, null, i, env.currentFileInfo, true)));
+ } else if (e) {
+ nodes.push(new(tree.Paren)(e));
+ } else {
+ return null;
+ }
+ } else { return null; }
+ }
+ } while (e);
+
+ if (nodes.length > 0) {
+ return new(tree.Expression)(nodes);
+ }
+ },
+
+ mediaFeatures: function () {
+ var e, features = [];
+
+ do {
+ if (e = $(this.mediaFeature)) {
+ features.push(e);
+ if (! $(',')) { break; }
+ } else if (e = $(this.entities.variable)) {
+ features.push(e);
+ if (! $(',')) { break; }
+ }
+ } while (e);
+
+ return features.length > 0 ? features : null;
+ },
+
+ media: function () {
+ var features, rules, media, debugInfo;
+
+ if (env.dumpLineNumbers) {
+ debugInfo = getDebugInfo(i, input, env);
+ }
+
+ if ($(/^@media/)) {
+ features = $(this.mediaFeatures);
+
+ if (rules = $(this.block)) {
+ media = new(tree.Media)(rules, features, i, env.currentFileInfo);
+ if (env.dumpLineNumbers) {
+ media.debugInfo = debugInfo;
+ }
+ return media;
+ }
+ }
+ },
+
+ //
+ // A CSS Directive
+ //
+ // @charset "utf-8";
+ //
+ directive: function () {
+ var name, value, rules, nonVendorSpecificName,
+ hasBlock, hasIdentifier, hasExpression, identifier;
+
+ if (input.charAt(i) !== '@') { return; }
+
+ if (value = $(this['import']) || $(this.media)) {
+ return value;
+ }
+
+ save();
+
+ name = $(/^@[a-z-]+/);
+
+ if (!name) { return; }
+
+ nonVendorSpecificName = name;
+ if (name.charAt(1) == '-' && name.indexOf('-', 2) > 0) {
+ nonVendorSpecificName = "@" + name.slice(name.indexOf('-', 2) + 1);
+ }
+
+ switch(nonVendorSpecificName) {
+ case "@font-face":
+ hasBlock = true;
+ break;
+ case "@viewport":
+ case "@top-left":
+ case "@top-left-corner":
+ case "@top-center":
+ case "@top-right":
+ case "@top-right-corner":
+ case "@bottom-left":
+ case "@bottom-left-corner":
+ case "@bottom-center":
+ case "@bottom-right":
+ case "@bottom-right-corner":
+ case "@left-top":
+ case "@left-middle":
+ case "@left-bottom":
+ case "@right-top":
+ case "@right-middle":
+ case "@right-bottom":
+ hasBlock = true;
+ break;
+ case "@host":
+ case "@page":
+ case "@document":
+ case "@supports":
+ case "@keyframes":
+ hasBlock = true;
+ hasIdentifier = true;
+ break;
+ case "@namespace":
+ hasExpression = true;
+ break;
+ }
+
+ if (hasIdentifier) {
+ identifier = ($(/^[^{]+/) || '').trim();
+ if (identifier) {
+ name += " " + identifier;
+ }
+ }
+
+ if (hasBlock)
+ {
+ if (rules = $(this.block)) {
+ return new(tree.Directive)(name, rules, i, env.currentFileInfo);
+ }
+ } else {
+ if ((value = hasExpression ? $(this.expression) : $(this.entity)) && $(';')) {
+ var directive = new(tree.Directive)(name, value, i, env.currentFileInfo);
+ if (env.dumpLineNumbers) {
+ directive.debugInfo = getDebugInfo(i, input, env);
+ }
+ return directive;
+ }
+ }
+
+ restore();
+ },
+
+ //
+ // A Value is a comma-delimited list of Expressions
+ //
+ // font-family: Baskerville, Georgia, serif;
+ //
+ // In a Rule, a Value represents everything after the `:`,
+ // and before the `;`.
+ //
+ value: function () {
+ var e, expressions = [];
+
+ while (e = $(this.expression)) {
+ expressions.push(e);
+ if (! $(',')) { break; }
+ }
+
+ if (expressions.length > 0) {
+ return new(tree.Value)(expressions);
+ }
+ },
+ important: function () {
+ if (input.charAt(i) === '!') {
+ return $(/^! *important/);
+ }
+ },
+ sub: function () {
+ var a, e;
+
+ if ($('(')) {
+ if (a = $(this.addition)) {
+ e = new(tree.Expression)([a]);
+ expect(')');
+ e.parens = true;
+ return e;
+ }
+ }
+ },
+ multiplication: function () {
+ var m, a, op, operation, isSpaced;
+ if (m = $(this.operand)) {
+ isSpaced = isWhitespace(input.charAt(i - 1));
+ while (!peek(/^\/[*\/]/) && (op = ($('/') || $('*')))) {
+ if (a = $(this.operand)) {
+ m.parensInOp = true;
+ a.parensInOp = true;
+ operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
+ isSpaced = isWhitespace(input.charAt(i - 1));
+ } else {
+ break;
+ }
+ }
+ return operation || m;
+ }
+ },
+ addition: function () {
+ var m, a, op, operation, isSpaced;
+ if (m = $(this.multiplication)) {
+ isSpaced = isWhitespace(input.charAt(i - 1));
+ while ((op = $(/^[-+]\s+/) || (!isSpaced && ($('+') || $('-')))) &&
+ (a = $(this.multiplication))) {
+ m.parensInOp = true;
+ a.parensInOp = true;
+ operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
+ isSpaced = isWhitespace(input.charAt(i - 1));
+ }
+ return operation || m;
+ }
+ },
+ conditions: function () {
+ var a, b, index = i, condition;
+
+ if (a = $(this.condition)) {
+ while (peek(/^,\s*(not\s*)?\(/) && $(',') && (b = $(this.condition))) {
+ condition = new(tree.Condition)('or', condition || a, b, index);
+ }
+ return condition || a;
+ }
+ },
+ condition: function () {
+ var a, b, c, op, index = i, negate = false;
+
+ if ($(/^not/)) { negate = true; }
+ expect('(');
+ if (a = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
+ if (op = $(/^(?:>=|<=|=<|[<=>])/)) {
+ if (b = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
+ c = new(tree.Condition)(op, a, b, index, negate);
+ } else {
+ error('expected expression');
+ }
+ } else {
+ c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index, negate);
+ }
+ expect(')');
+ return $(/^and/) ? new(tree.Condition)('and', c, $(this.condition)) : c;
+ }
+ },
+
+ //
+ // An operand is anything that can be part of an operation,
+ // such as a Color, or a Variable
+ //
+ operand: function () {
+ var negate, p = input.charAt(i + 1);
+
+ if (input.charAt(i) === '-' && (p === '@' || p === '(')) { negate = $('-'); }
+ var o = $(this.sub) || $(this.entities.dimension) ||
+ $(this.entities.color) || $(this.entities.variable) ||
+ $(this.entities.call);
+
+ if (negate) {
+ o.parensInOp = true;
+ o = new(tree.Negative)(o);
+ }
+
+ return o;
+ },
+
+ //
+ // Expressions either represent mathematical operations,
+ // or white-space delimited Entities.
+ //
+ // 1px solid black
+ // @var * 2
+ //
+ expression: function () {
+ var e, delim, entities = [];
+
+ while (e = $(this.addition) || $(this.entity)) {
+ entities.push(e);
+ // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
+ if (!peek(/^\/[\/*]/) && (delim = $('/'))) {
+ entities.push(new(tree.Anonymous)(delim));
+ }
+ }
+ if (entities.length > 0) {
+ return new(tree.Expression)(entities);
+ }
+ },
+ property: function () {
+ var name;
+
+ if (name = $(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/)) {
+ return name[1];
+ }
+ },
+ ruleProperty: function () {
+ var name;
+
+ if (name = $(/^(\*?-?[_a-zA-Z0-9-]+)\s*(\+?)\s*:/)) {
+ return name[1] + (name[2] || "");
+ }
+ }
+ }
+ };
+};
+
+
+(function (tree) {
+
+tree.functions = {
+ rgb: function (r, g, b) {
+ return this.rgba(r, g, b, 1.0);
+ },
+ rgba: function (r, g, b, a) {
+ var rgb = [r, g, b].map(function (c) { return scaled(c, 256); });
+ a = number(a);
+ return new(tree.Color)(rgb, a);
+ },
+ hsl: function (h, s, l) {
+ return this.hsla(h, s, l, 1.0);
+ },
+ hsla: function (h, s, l, a) {
+ function hue(h) {
+ h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h);
+ if (h * 6 < 1) { return m1 + (m2 - m1) * h * 6; }
+ else if (h * 2 < 1) { return m2; }
+ else if (h * 3 < 2) { return m1 + (m2 - m1) * (2/3 - h) * 6; }
+ else { return m1; }
+ }
+
+ h = (number(h) % 360) / 360;
+ s = clamp(number(s)); l = clamp(number(l)); a = clamp(number(a));
+
+ var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
+ var m1 = l * 2 - m2;
+
+ return this.rgba(hue(h + 1/3) * 255,
+ hue(h) * 255,
+ hue(h - 1/3) * 255,
+ a);
+ },
+
+ hsv: function(h, s, v) {
+ return this.hsva(h, s, v, 1.0);
+ },
+
+ hsva: function(h, s, v, a) {
+ h = ((number(h) % 360) / 360) * 360;
+ s = number(s); v = number(v); a = number(a);
+
+ var i, f;
+ i = Math.floor((h / 60) % 6);
+ f = (h / 60) - i;
+
+ var vs = [v,
+ v * (1 - s),
+ v * (1 - f * s),
+ v * (1 - (1 - f) * s)];
+ var perm = [[0, 3, 1],
+ [2, 0, 1],
+ [1, 0, 3],
+ [1, 2, 0],
+ [3, 1, 0],
+ [0, 1, 2]];
+
+ return this.rgba(vs[perm[i][0]] * 255,
+ vs[perm[i][1]] * 255,
+ vs[perm[i][2]] * 255,
+ a);
+ },
+
+ hue: function (color) {
+ return new(tree.Dimension)(Math.round(color.toHSL().h));
+ },
+ saturation: function (color) {
+ return new(tree.Dimension)(Math.round(color.toHSL().s * 100), '%');
+ },
+ lightness: function (color) {
+ return new(tree.Dimension)(Math.round(color.toHSL().l * 100), '%');
+ },
+ hsvhue: function(color) {
+ return new(tree.Dimension)(Math.round(color.toHSV().h));
+ },
+ hsvsaturation: function (color) {
+ return new(tree.Dimension)(Math.round(color.toHSV().s * 100), '%');
+ },
+ hsvvalue: function (color) {
+ return new(tree.Dimension)(Math.round(color.toHSV().v * 100), '%');
+ },
+ red: function (color) {
+ return new(tree.Dimension)(color.rgb[0]);
+ },
+ green: function (color) {
+ return new(tree.Dimension)(color.rgb[1]);
+ },
+ blue: function (color) {
+ return new(tree.Dimension)(color.rgb[2]);
+ },
+ alpha: function (color) {
+ return new(tree.Dimension)(color.toHSL().a);
+ },
+ luma: function (color) {
+ return new(tree.Dimension)(Math.round(color.luma() * color.alpha * 100), '%');
+ },
+ saturate: function (color, amount) {
+ // filter: saturate(3.2);
+ // should be kept as is, so check for color
+ if (!color.rgb) {
+ return null;
+ }
+ var hsl = color.toHSL();
+
+ hsl.s += amount.value / 100;
+ hsl.s = clamp(hsl.s);
+ return hsla(hsl);
+ },
+ desaturate: function (color, amount) {
+ var hsl = color.toHSL();
+
+ hsl.s -= amount.value / 100;
+ hsl.s = clamp(hsl.s);
+ return hsla(hsl);
+ },
+ lighten: function (color, amount) {
+ var hsl = color.toHSL();
+
+ hsl.l += amount.value / 100;
+ hsl.l = clamp(hsl.l);
+ return hsla(hsl);
+ },
+ darken: function (color, amount) {
+ var hsl = color.toHSL();
+
+ hsl.l -= amount.value / 100;
+ hsl.l = clamp(hsl.l);
+ return hsla(hsl);
+ },
+ fadein: function (color, amount) {
+ var hsl = color.toHSL();
+
+ hsl.a += amount.value / 100;
+ hsl.a = clamp(hsl.a);
+ return hsla(hsl);
+ },
+ fadeout: function (color, amount) {
+ var hsl = color.toHSL();
+
+ hsl.a -= amount.value / 100;
+ hsl.a = clamp(hsl.a);
+ return hsla(hsl);
+ },
+ fade: function (color, amount) {
+ var hsl = color.toHSL();
+
+ hsl.a = amount.value / 100;
+ hsl.a = clamp(hsl.a);
+ return hsla(hsl);
+ },
+ spin: function (color, amount) {
+ var hsl = color.toHSL();
+ var hue = (hsl.h + amount.value) % 360;
+
+ hsl.h = hue < 0 ? 360 + hue : hue;
+
+ return hsla(hsl);
+ },
+ //
+ // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
+ // http://sass-lang.com
+ //
+ mix: function (color1, color2, weight) {
+ if (!weight) {
+ weight = new(tree.Dimension)(50);
+ }
+ var p = weight.value / 100.0;
+ var w = p * 2 - 1;
+ var a = color1.toHSL().a - color2.toHSL().a;
+
+ var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
+ var w2 = 1 - w1;
+
+ var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2,
+ color1.rgb[1] * w1 + color2.rgb[1] * w2,
+ color1.rgb[2] * w1 + color2.rgb[2] * w2];
+
+ var alpha = color1.alpha * p + color2.alpha * (1 - p);
+
+ return new(tree.Color)(rgb, alpha);
+ },
+ greyscale: function (color) {
+ return this.desaturate(color, new(tree.Dimension)(100));
+ },
+ contrast: function (color, dark, light, threshold) {
+ // filter: contrast(3.2);
+ // should be kept as is, so check for color
+ if (!color.rgb) {
+ return null;
+ }
+ if (typeof light === 'undefined') {
+ light = this.rgba(255, 255, 255, 1.0);
+ }
+ if (typeof dark === 'undefined') {
+ dark = this.rgba(0, 0, 0, 1.0);
+ }
+ //Figure out which is actually light and dark!
+ if (dark.luma() > light.luma()) {
+ var t = light;
+ light = dark;
+ dark = t;
+ }
+ if (typeof threshold === 'undefined') {
+ threshold = 0.43;
+ } else {
+ threshold = number(threshold);
+ }
+ if ((color.luma() * color.alpha) < threshold) {
+ return light;
+ } else {
+ return dark;
+ }
+ },
+ e: function (str) {
+ return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str);
+ },
+ escape: function (str) {
+ return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29"));
+ },
+ '%': function (quoted /* arg, arg, ...*/) {
+ var args = Array.prototype.slice.call(arguments, 1),
+ str = quoted.value;
+
+ for (var i = 0; i < args.length; i++) {
+ /*jshint loopfunc:true */
+ str = str.replace(/%[sda]/i, function(token) {
+ var value = token.match(/s/i) ? args[i].value : args[i].toCSS();
+ return token.match(/[A-Z]$/) ? encodeURIComponent(value) : value;
+ });
+ }
+ str = str.replace(/%%/g, '%');
+ return new(tree.Quoted)('"' + str + '"', str);
+ },
+ unit: function (val, unit) {
+ if(!(val instanceof tree.Dimension)) {
+ throw { type: "Argument", message: "the first argument to unit must be a number" + (val instanceof tree.Operation ? ". Have you forgotten parenthesis?" : "") };
+ }
+ return new(tree.Dimension)(val.value, unit ? unit.toCSS() : "");
+ },
+ convert: function (val, unit) {
+ return val.convertTo(unit.value);
+ },
+ round: function (n, f) {
+ var fraction = typeof(f) === "undefined" ? 0 : f.value;
+ return this._math(function(num) { return num.toFixed(fraction); }, null, n);
+ },
+ pi: function () {
+ return new(tree.Dimension)(Math.PI);
+ },
+ mod: function(a, b) {
+ return new(tree.Dimension)(a.value % b.value, a.unit);
+ },
+ pow: function(x, y) {
+ if (typeof x === "number" && typeof y === "number") {
+ x = new(tree.Dimension)(x);
+ y = new(tree.Dimension)(y);
+ } else if (!(x instanceof tree.Dimension) || !(y instanceof tree.Dimension)) {
+ throw { type: "Argument", message: "arguments must be numbers" };
+ }
+
+ return new(tree.Dimension)(Math.pow(x.value, y.value), x.unit);
+ },
+ _math: function (fn, unit, n) {
+ if (n instanceof tree.Dimension) {
+ /*jshint eqnull:true */
+ return new(tree.Dimension)(fn(parseFloat(n.value)), unit == null ? n.unit : unit);
+ } else if (typeof(n) === 'number') {
+ return fn(n);
+ } else {
+ throw { type: "Argument", message: "argument must be a number" };
+ }
+ },
+ _minmax: function (isMin, args) {
+ args = Array.prototype.slice.call(args);
+ switch(args.length) {
+ case 0: throw { type: "Argument", message: "one or more arguments required" };
+ case 1: return args[0];
+ }
+ var i, j, current, currentUnified, referenceUnified, unit,
+ order = [], // elems only contains original argument values.
+ values = {}; // key is the unit.toString() for unified tree.Dimension values,
+ // value is the index into the order array.
+ for (i = 0; i < args.length; i++) {
+ current = args[i];
+ if (!(current instanceof tree.Dimension)) {
+ order.push(current);
+ continue;
+ }
+ currentUnified = current.unify();
+ unit = currentUnified.unit.toString();
+ j = values[unit];
+ if (j === undefined) {
+ values[unit] = order.length;
+ order.push(current);
+ continue;
+ }
+ referenceUnified = order[j].unify();
+ if ( isMin && currentUnified.value < referenceUnified.value ||
+ !isMin && currentUnified.value > referenceUnified.value) {
+ order[j] = current;
+ }
+ }
+ if (order.length == 1) {
+ return order[0];
+ }
+ args = order.map(function (a) { return a.toCSS(this.env); })
+ .join(this.env.compress ? "," : ", ");
+ return new(tree.Anonymous)((isMin ? "min" : "max") + "(" + args + ")");
+ },
+ min: function () {
+ return this._minmax(true, arguments);
+ },
+ max: function () {
+ return this._minmax(false, arguments);
+ },
+ argb: function (color) {
+ return new(tree.Anonymous)(color.toARGB());
+
+ },
+ percentage: function (n) {
+ return new(tree.Dimension)(n.value * 100, '%');
+ },
+ color: function (n) {
+ if (n instanceof tree.Quoted) {
+ var colorCandidate = n.value,
+ returnColor;
+ returnColor = tree.Color.fromKeyword(colorCandidate);
+ if (returnColor) {
+ return returnColor;
+ }
+ if (/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/.test(colorCandidate)) {
+ return new(tree.Color)(colorCandidate.slice(1));
+ }
+ throw { type: "Argument", message: "argument must be a color keyword or 3/6 digit hex e.g. #FFF" };
+ } else {
+ throw { type: "Argument", message: "argument must be a string" };
+ }
+ },
+ iscolor: function (n) {
+ return this._isa(n, tree.Color);
+ },
+ isnumber: function (n) {
+ return this._isa(n, tree.Dimension);
+ },
+ isstring: function (n) {
+ return this._isa(n, tree.Quoted);
+ },
+ iskeyword: function (n) {
+ return this._isa(n, tree.Keyword);
+ },
+ isurl: function (n) {
+ return this._isa(n, tree.URL);
+ },
+ ispixel: function (n) {
+ return this.isunit(n, 'px');
+ },
+ ispercentage: function (n) {
+ return this.isunit(n, '%');
+ },
+ isem: function (n) {
+ return this.isunit(n, 'em');
+ },
+ isunit: function (n, unit) {
+ return (n instanceof tree.Dimension) && n.unit.is(unit.value || unit) ? tree.True : tree.False;
+ },
+ _isa: function (n, Type) {
+ return (n instanceof Type) ? tree.True : tree.False;
+ },
+
+ /* Blending modes */
+
+ multiply: function(color1, color2) {
+ var r = color1.rgb[0] * color2.rgb[0] / 255;
+ var g = color1.rgb[1] * color2.rgb[1] / 255;
+ var b = color1.rgb[2] * color2.rgb[2] / 255;
+ return this.rgb(r, g, b);
+ },
+ screen: function(color1, color2) {
+ var r = 255 - (255 - color1.rgb[0]) * (255 - color2.rgb[0]) / 255;
+ var g = 255 - (255 - color1.rgb[1]) * (255 - color2.rgb[1]) / 255;
+ var b = 255 - (255 - color1.rgb[2]) * (255 - color2.rgb[2]) / 255;
+ return this.rgb(r, g, b);
+ },
+ overlay: function(color1, color2) {
+ var r = color1.rgb[0] < 128 ? 2 * color1.rgb[0] * color2.rgb[0] / 255 : 255 - 2 * (255 - color1.rgb[0]) * (255 - color2.rgb[0]) / 255;
+ var g = color1.rgb[1] < 128 ? 2 * color1.rgb[1] * color2.rgb[1] / 255 : 255 - 2 * (255 - color1.rgb[1]) * (255 - color2.rgb[1]) / 255;
+ var b = color1.rgb[2] < 128 ? 2 * color1.rgb[2] * color2.rgb[2] / 255 : 255 - 2 * (255 - color1.rgb[2]) * (255 - color2.rgb[2]) / 255;
+ return this.rgb(r, g, b);
+ },
+ softlight: function(color1, color2) {
+ var t = color2.rgb[0] * color1.rgb[0] / 255;
+ var r = t + color1.rgb[0] * (255 - (255 - color1.rgb[0]) * (255 - color2.rgb[0]) / 255 - t) / 255;
+ t = color2.rgb[1] * color1.rgb[1] / 255;
+ var g = t + color1.rgb[1] * (255 - (255 - color1.rgb[1]) * (255 - color2.rgb[1]) / 255 - t) / 255;
+ t = color2.rgb[2] * color1.rgb[2] / 255;
+ var b = t + color1.rgb[2] * (255 - (255 - color1.rgb[2]) * (255 - color2.rgb[2]) / 255 - t) / 255;
+ return this.rgb(r, g, b);
+ },
+ hardlight: function(color1, color2) {
+ var r = color2.rgb[0] < 128 ? 2 * color2.rgb[0] * color1.rgb[0] / 255 : 255 - 2 * (255 - color2.rgb[0]) * (255 - color1.rgb[0]) / 255;
+ var g = color2.rgb[1] < 128 ? 2 * color2.rgb[1] * color1.rgb[1] / 255 : 255 - 2 * (255 - color2.rgb[1]) * (255 - color1.rgb[1]) / 255;
+ var b = color2.rgb[2] < 128 ? 2 * color2.rgb[2] * color1.rgb[2] / 255 : 255 - 2 * (255 - color2.rgb[2]) * (255 - color1.rgb[2]) / 255;
+ return this.rgb(r, g, b);
+ },
+ difference: function(color1, color2) {
+ var r = Math.abs(color1.rgb[0] - color2.rgb[0]);
+ var g = Math.abs(color1.rgb[1] - color2.rgb[1]);
+ var b = Math.abs(color1.rgb[2] - color2.rgb[2]);
+ return this.rgb(r, g, b);
+ },
+ exclusion: function(color1, color2) {
+ var r = color1.rgb[0] + color2.rgb[0] * (255 - color1.rgb[0] - color1.rgb[0]) / 255;
+ var g = color1.rgb[1] + color2.rgb[1] * (255 - color1.rgb[1] - color1.rgb[1]) / 255;
+ var b = color1.rgb[2] + color2.rgb[2] * (255 - color1.rgb[2] - color1.rgb[2]) / 255;
+ return this.rgb(r, g, b);
+ },
+ average: function(color1, color2) {
+ var r = (color1.rgb[0] + color2.rgb[0]) / 2;
+ var g = (color1.rgb[1] + color2.rgb[1]) / 2;
+ var b = (color1.rgb[2] + color2.rgb[2]) / 2;
+ return this.rgb(r, g, b);
+ },
+ negation: function(color1, color2) {
+ var r = 255 - Math.abs(255 - color2.rgb[0] - color1.rgb[0]);
+ var g = 255 - Math.abs(255 - color2.rgb[1] - color1.rgb[1]);
+ var b = 255 - Math.abs(255 - color2.rgb[2] - color1.rgb[2]);
+ return this.rgb(r, g, b);
+ },
+ tint: function(color, amount) {
+ return this.mix(this.rgb(255,255,255), color, amount);
+ },
+ shade: function(color, amount) {
+ return this.mix(this.rgb(0, 0, 0), color, amount);
+ },
+ extract: function(values, index) {
+ index = index.value - 1; // (1-based index)
+ // handle non-array values as an array of length 1
+ // return 'undefined' if index is invalid
+ return Array.isArray(values.value)
+ ? values.value[index] : Array(values)[index];
+ },
+ length: function(values) {
+ var n = Array.isArray(values.value) ? values.value.length : 1;
+ return new tree.Dimension(n);
+ },
+
+ "data-uri": function(mimetypeNode, filePathNode) {
+
+ if (typeof window !== 'undefined') {
+ return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
+ }
+
+ var mimetype = mimetypeNode.value;
+ var filePath = (filePathNode && filePathNode.value);
+
+ var fs = require("fs"),
+ path = require("path"),
+ useBase64 = false;
+
+ if (arguments.length < 2) {
+ filePath = mimetype;
+ }
+
+ if (this.env.isPathRelative(filePath)) {
+ if (this.currentFileInfo.relativeUrls) {
+ filePath = path.join(this.currentFileInfo.currentDirectory, filePath);
+ } else {
+ filePath = path.join(this.currentFileInfo.entryPath, filePath);
+ }
+ }
+
+ // detect the mimetype if not given
+ if (arguments.length < 2) {
+ var mime;
+ try {
+ mime = require('mime');
+ } catch (ex) {
+ mime = tree._mime;
+ }
+
+ mimetype = mime.lookup(filePath);
+
+ // use base 64 unless it's an ASCII or UTF-8 format
+ var charset = mime.charsets.lookup(mimetype);
+ useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
+ if (useBase64) { mimetype += ';base64'; }
+ }
+ else {
+ useBase64 = /;base64$/.test(mimetype);
+ }
+
+ var buf = fs.readFileSync(filePath);
+
+ // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
+ // and the --ieCompat flag is enabled, return a normal url() instead.
+ var DATA_URI_MAX_KB = 32,
+ fileSizeInKB = parseInt((buf.length / 1024), 10);
+ if (fileSizeInKB >= DATA_URI_MAX_KB) {
+
+ if (this.env.ieCompat !== false) {
+ if (!this.env.silent) {
+ console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB);
+ }
+
+ return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env);
+ }
+ }
+
+ buf = useBase64 ? buf.toString('base64')
+ : encodeURIComponent(buf);
+
+ var uri = "'data:" + mimetype + ',' + buf + "'";
+ return new(tree.URL)(new(tree.Anonymous)(uri));
+ },
+
+ "svg-gradient": function(direction) {
+
+ function throwArgumentDescriptor() {
+ throw { type: "Argument", message: "svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]" };
+ }
+
+ if (arguments.length < 3) {
+ throwArgumentDescriptor();
+ }
+ var stops = Array.prototype.slice.call(arguments, 1),
+ gradientDirectionSvg,
+ gradientType = "linear",
+ rectangleDimension = 'x="0" y="0" width="1" height="1"',
+ useBase64 = true,
+ renderEnv = {compress: false},
+ returner,
+ directionValue = direction.toCSS(renderEnv),
+ i, color, position, positionValue, alpha;
+
+ switch (directionValue) {
+ case "to bottom":
+ gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
+ break;
+ case "to right":
+ gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
+ break;
+ case "to bottom right":
+ gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
+ break;
+ case "to top right":
+ gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
+ break;
+ case "ellipse":
+ case "ellipse at center":
+ gradientType = "radial";
+ gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
+ rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
+ break;
+ default:
+ throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" };
+ }
+ returner = '<?xml version="1.0" ?>' +
+ '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' +
+ '<' + gradientType + 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' + gradientDirectionSvg + '>';
+
+ for (i = 0; i < stops.length; i+= 1) {
+ if (stops[i].value) {
+ color = stops[i].value[0];
+ position = stops[i].value[1];
+ } else {
+ color = stops[i];
+ position = undefined;
+ }
+
+ if (!(color instanceof tree.Color) || (!((i === 0 || i+1 === stops.length) && position === undefined) && !(position instanceof tree.Dimension))) {
+ throwArgumentDescriptor();
+ }
+ positionValue = position ? position.toCSS(renderEnv) : i === 0 ? "0%" : "100%";
+ alpha = color.alpha;
+ returner += '<stop offset="' + positionValue + '" stop-color="' + color.toRGB() + '"' + (alpha < 1 ? ' stop-opacity="' + alpha + '"' : '') + '/>';
+ }
+ returner += '</' + gradientType + 'Gradient>' +
+ '<rect ' + rectangleDimension + ' fill="url(#gradient)" /></svg>';
+
+ if (useBase64) {
+ // only works in node, needs interface to what is supported in environment
+ try {
+ returner = new Buffer(returner).toString('base64');
+ } catch(e) {
+ useBase64 = false;
+ }
+ }
+
+ returner = "'data:image/svg+xml" + (useBase64 ? ";base64" : "") + "," + returner + "'";
+ return new(tree.URL)(new(tree.Anonymous)(returner));
+ }
+};
+
+// these static methods are used as a fallback when the optional 'mime' dependency is missing
+tree._mime = {
+ // this map is intentionally incomplete
+ // if you want more, install 'mime' dep
+ _types: {
+ '.htm' : 'text/html',
+ '.html': 'text/html',
+ '.gif' : 'image/gif',
+ '.jpg' : 'image/jpeg',
+ '.jpeg': 'image/jpeg',
+ '.png' : 'image/png'
+ },
+ lookup: function (filepath) {
+ var ext = require('path').extname(filepath),
+ type = tree._mime._types[ext];
+ if (type === undefined) {
+ throw new Error('Optional dependency "mime" is required for ' + ext);
+ }
+ return type;
+ },
+ charsets: {
+ lookup: function (type) {
+ // assumes all text types are UTF-8
+ return type && (/^text\//).test(type) ? 'UTF-8' : '';
+ }
+ }
+};
+
+var mathFunctions = [{name:"ceil"}, {name:"floor"}, {name: "sqrt"}, {name:"abs"},
+ {name:"tan", unit: ""}, {name:"sin", unit: ""}, {name:"cos", unit: ""},
+ {name:"atan", unit: "rad"}, {name:"asin", unit: "rad"}, {name:"acos", unit: "rad"}],
+ createMathFunction = function(name, unit) {
+ return function(n) {
+ /*jshint eqnull:true */
+ if (unit != null) {
+ n = n.unify();
+ }
+ return this._math(Math[name], unit, n);
+ };
+ };
+
+for(var i = 0; i < mathFunctions.length; i++) {
+ tree.functions[mathFunctions[i].name] = createMathFunction(mathFunctions[i].name, mathFunctions[i].unit);
+}
+
+function hsla(color) {
+ return tree.functions.hsla(color.h, color.s, color.l, color.a);
+}
+
+function scaled(n, size) {
+ if (n instanceof tree.Dimension && n.unit.is('%')) {
+ return parseFloat(n.value * size / 100);
+ } else {
+ return number(n);
+ }
+}
+
+function number(n) {
+ if (n instanceof tree.Dimension) {
+ return parseFloat(n.unit.is('%') ? n.value / 100 : n.value);
+ } else if (typeof(n) === 'number') {
+ return n;
+ } else {
+ throw {
+ error: "RuntimeError",
+ message: "color functions take numbers as parameters"
+ };
+ }
+}
+
+function clamp(val) {
+ return Math.min(1, Math.max(0, val));
+}
+
+tree.functionCall = function(env, currentFileInfo) {
+ this.env = env;
+ this.currentFileInfo = currentFileInfo;
+};
+
+tree.functionCall.prototype = tree.functions;
+
+})(require('./tree'));
+
+(function (tree) {
+ tree.colors = {
+ 'aliceblue':'#f0f8ff',
+ 'antiquewhite':'#faebd7',
+ 'aqua':'#00ffff',
+ 'aquamarine':'#7fffd4',
+ 'azure':'#f0ffff',
+ 'beige':'#f5f5dc',
+ 'bisque':'#ffe4c4',
+ 'black':'#000000',
+ 'blanchedalmond':'#ffebcd',
+ 'blue':'#0000ff',
+ 'blueviolet':'#8a2be2',
+ 'brown':'#a52a2a',
+ 'burlywood':'#deb887',
+ 'cadetblue':'#5f9ea0',
+ 'chartreuse':'#7fff00',
+ 'chocolate':'#d2691e',
+ 'coral':'#ff7f50',
+ 'cornflowerblue':'#6495ed',
+ 'cornsilk':'#fff8dc',
+ 'crimson':'#dc143c',
+ 'cyan':'#00ffff',
+ 'darkblue':'#00008b',
+ 'darkcyan':'#008b8b',
+ 'darkgoldenrod':'#b8860b',
+ 'darkgray':'#a9a9a9',
+ 'darkgrey':'#a9a9a9',
+ 'darkgreen':'#006400',
+ 'darkkhaki':'#bdb76b',
+ 'darkmagenta':'#8b008b',
+ 'darkolivegreen':'#556b2f',
+ 'darkorange':'#ff8c00',
+ 'darkorchid':'#9932cc',
+ 'darkred':'#8b0000',
+ 'darksalmon':'#e9967a',
+ 'darkseagreen':'#8fbc8f',
+ 'darkslateblue':'#483d8b',
+ 'darkslategray':'#2f4f4f',
+ 'darkslategrey':'#2f4f4f',
+ 'darkturquoise':'#00ced1',
+ 'darkviolet':'#9400d3',
+ 'deeppink':'#ff1493',
+ 'deepskyblue':'#00bfff',
+ 'dimgray':'#696969',
+ 'dimgrey':'#696969',
+ 'dodgerblue':'#1e90ff',
+ 'firebrick':'#b22222',
+ 'floralwhite':'#fffaf0',
+ 'forestgreen':'#228b22',
+ 'fuchsia':'#ff00ff',
+ 'gainsboro':'#dcdcdc',
+ 'ghostwhite':'#f8f8ff',
+ 'gold':'#ffd700',
+ 'goldenrod':'#daa520',
+ 'gray':'#808080',
+ 'grey':'#808080',
+ 'green':'#008000',
+ 'greenyellow':'#adff2f',
+ 'honeydew':'#f0fff0',
+ 'hotpink':'#ff69b4',
+ 'indianred':'#cd5c5c',
+ 'indigo':'#4b0082',
+ 'ivory':'#fffff0',
+ 'khaki':'#f0e68c',
+ 'lavender':'#e6e6fa',
+ 'lavenderblush':'#fff0f5',
+ 'lawngreen':'#7cfc00',
+ 'lemonchiffon':'#fffacd',
+ 'lightblue':'#add8e6',
+ 'lightcoral':'#f08080',
+ 'lightcyan':'#e0ffff',
+ 'lightgoldenrodyellow':'#fafad2',
+ 'lightgray':'#d3d3d3',
+ 'lightgrey':'#d3d3d3',
+ 'lightgreen':'#90ee90',
+ 'lightpink':'#ffb6c1',
+ 'lightsalmon':'#ffa07a',
+ 'lightseagreen':'#20b2aa',
+ 'lightskyblue':'#87cefa',
+ 'lightslategray':'#778899',
+ 'lightslategrey':'#778899',
+ 'lightsteelblue':'#b0c4de',
+ 'lightyellow':'#ffffe0',
+ 'lime':'#00ff00',
+ 'limegreen':'#32cd32',
+ 'linen':'#faf0e6',
+ 'magenta':'#ff00ff',
+ 'maroon':'#800000',
+ 'mediumaquamarine':'#66cdaa',
+ 'mediumblue':'#0000cd',
+ 'mediumorchid':'#ba55d3',
+ 'mediumpurple':'#9370d8',
+ 'mediumseagreen':'#3cb371',
+ 'mediumslateblue':'#7b68ee',
+ 'mediumspringgreen':'#00fa9a',
+ 'mediumturquoise':'#48d1cc',
+ 'mediumvioletred':'#c71585',
+ 'midnightblue':'#191970',
+ 'mintcream':'#f5fffa',
+ 'mistyrose':'#ffe4e1',
+ 'moccasin':'#ffe4b5',
+ 'navajowhite':'#ffdead',
+ 'navy':'#000080',
+ 'oldlace':'#fdf5e6',
+ 'olive':'#808000',
+ 'olivedrab':'#6b8e23',
+ 'orange':'#ffa500',
+ 'orangered':'#ff4500',
+ 'orchid':'#da70d6',
+ 'palegoldenrod':'#eee8aa',
+ 'palegreen':'#98fb98',
+ 'paleturquoise':'#afeeee',
+ 'palevioletred':'#d87093',
+ 'papayawhip':'#ffefd5',
+ 'peachpuff':'#ffdab9',
+ 'peru':'#cd853f',
+ 'pink':'#ffc0cb',
+ 'plum':'#dda0dd',
+ 'powderblue':'#b0e0e6',
+ 'purple':'#800080',
+ 'red':'#ff0000',
+ 'rosybrown':'#bc8f8f',
+ 'royalblue':'#4169e1',
+ 'saddlebrown':'#8b4513',
+ 'salmon':'#fa8072',
+ 'sandybrown':'#f4a460',
+ 'seagreen':'#2e8b57',
+ 'seashell':'#fff5ee',
+ 'sienna':'#a0522d',
+ 'silver':'#c0c0c0',
+ 'skyblue':'#87ceeb',
+ 'slateblue':'#6a5acd',
+ 'slategray':'#708090',
+ 'slategrey':'#708090',
+ 'snow':'#fffafa',
+ 'springgreen':'#00ff7f',
+ 'steelblue':'#4682b4',
+ 'tan':'#d2b48c',
+ 'teal':'#008080',
+ 'thistle':'#d8bfd8',
+ 'tomato':'#ff6347',
+ 'turquoise':'#40e0d0',
+ 'violet':'#ee82ee',
+ 'wheat':'#f5deb3',
+ 'white':'#ffffff',
+ 'whitesmoke':'#f5f5f5',
+ 'yellow':'#ffff00',
+ 'yellowgreen':'#9acd32'
+ };
+})(require('./tree'));
+
+(function (tree) {
+
+tree.debugInfo = function(env, ctx, lineSeperator) {
+ var result="";
+ if (env.dumpLineNumbers && !env.compress) {
+ switch(env.dumpLineNumbers) {
+ case 'comments':
+ result = tree.debugInfo.asComment(ctx);
+ break;
+ case 'mediaquery':
+ result = tree.debugInfo.asMediaQuery(ctx);
+ break;
+ case 'all':
+ result = tree.debugInfo.asComment(ctx) + (lineSeperator || "") + tree.debugInfo.asMediaQuery(ctx);
+ break;
+ }
+ }
+ return result;
+};
+
+tree.debugInfo.asComment = function(ctx) {
+ return '/* line ' + ctx.debugInfo.lineNumber + ', ' + ctx.debugInfo.fileName + ' */\n';
+};
+
+tree.debugInfo.asMediaQuery = function(ctx) {
+ return '@media -sass-debug-info{filename{font-family:' +
+ ('file://' + ctx.debugInfo.fileName).replace(/([.:/\\])/g, function (a) {
+ if (a == '\\') {
+ a = '\/';
+ }
+ return '\\' + a;
+ }) +
+ '}line{font-family:\\00003' + ctx.debugInfo.lineNumber + '}}\n';
+};
+
+tree.find = function (obj, fun) {
+ for (var i = 0, r; i < obj.length; i++) {
+ if (r = fun.call(obj, obj[i])) { return r; }
+ }
+ return null;
+};
+
+tree.jsify = function (obj) {
+ if (Array.isArray(obj.value) && (obj.value.length > 1)) {
+ return '[' + obj.value.map(function (v) { return v.toCSS(false); }).join(', ') + ']';
+ } else {
+ return obj.toCSS(false);
+ }
+};
+
+tree.toCSS = function (env) {
+ var strs = [];
+ this.genCSS(env, {
+ add: function(chunk, fileInfo, index) {
+ strs.push(chunk);
+ },
+ isEmpty: function () {
+ return strs.length === 0;
+ }
+ });
+ return strs.join('');
+};
+
+tree.outputRuleset = function (env, output, rules) {
+ output.add((env.compress ? '{' : ' {\n'));
+ env.tabLevel = (env.tabLevel || 0) + 1;
+ var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join(" "),
+ tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" ");
+ for(var i = 0; i < rules.length; i++) {
+ output.add(tabRuleStr);
+ rules[i].genCSS(env, output);
+ output.add(env.compress ? '' : '\n');
+ }
+ env.tabLevel--;
+ output.add(tabSetStr + "}");
+};
+
+})(require('./tree'));
+
+(function (tree) {
+
+tree.Alpha = function (val) {
+ this.value = val;
+};
+tree.Alpha.prototype = {
+ type: "Alpha",
+ accept: function (visitor) {
+ this.value = visitor.visit(this.value);
+ },
+ eval: function (env) {
+ if (this.value.eval) { return new tree.Alpha(this.value.eval(env)); }
+ return this;
+ },
+ genCSS: function (env, output) {
+ output.add("alpha(opacity=");
+
+ if (this.value.genCSS) {
+ this.value.genCSS(env, output);
+ } else {
+ output.add(this.value);
+ }
+
+ output.add(")");
+ },
+ toCSS: tree.toCSS
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Anonymous = function (string, index, currentFileInfo, mapLines) {
+ this.value = string.value || string;
+ this.index = index;
+ this.mapLines = mapLines;
+ this.currentFileInfo = currentFileInfo;
+};
+tree.Anonymous.prototype = {
+ type: "Anonymous",
+ eval: function () { return this; },
+ compare: function (x) {
+ if (!x.toCSS) {
+ return -1;
+ }
+
+ var left = this.toCSS(),
+ right = x.toCSS();
+
+ if (left === right) {
+ return 0;
+ }
+
+ return left < right ? -1 : 1;
+ },
+ genCSS: function (env, output) {
+ output.add(this.value, this.currentFileInfo, this.index, this.mapLines);
+ },
+ toCSS: tree.toCSS
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Assignment = function (key, val) {
+ this.key = key;
+ this.value = val;
+};
+tree.Assignment.prototype = {
+ type: "Assignment",
+ accept: function (visitor) {
+ this.value = visitor.visit(this.value);
+ },
+ eval: function (env) {
+ if (this.value.eval) {
+ return new(tree.Assignment)(this.key, this.value.eval(env));
+ }
+ return this;
+ },
+ genCSS: function (env, output) {
+ output.add(this.key + '=');
+ if (this.value.genCSS) {
+ this.value.genCSS(env, output);
+ } else {
+ output.add(this.value);
+ }
+ },
+ toCSS: tree.toCSS
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+//
+// A function call node.
+//
+tree.Call = function (name, args, index, currentFileInfo) {
+ this.name = name;
+ this.args = args;
+ this.index = index;
+ this.currentFileInfo = currentFileInfo;
+};
+tree.Call.prototype = {
+ type: "Call",
+ accept: function (visitor) {
+ this.args = visitor.visit(this.args);
+ },
+ //
+ // When evaluating a function call,
+ // we either find the function in `tree.functions` [1],
+ // in which case we call it, passing the evaluated arguments,
+ // if this returns null or we cannot find the function, we
+ // simply print it out as it appeared originally [2].
+ //
+ // The *functions.js* file contains the built-in functions.
+ //
+ // The reason why we evaluate the arguments, is in the case where
+ // we try to pass a variable to a function, like: `saturate(@color)`.
+ // The function should receive the value, not the variable.
+ //
+ eval: function (env) {
+ var args = this.args.map(function (a) { return a.eval(env); }),
+ nameLC = this.name.toLowerCase(),
+ result, func;
+
+ if (nameLC in tree.functions) { // 1.
+ try {
+ func = new tree.functionCall(env, this.currentFileInfo);
+ result = func[nameLC].apply(func, args);
+ /*jshint eqnull:true */
+ if (result != null) {
+ return result;
+ }
+ } catch (e) {
+ throw { type: e.type || "Runtime",
+ message: "error evaluating function `" + this.name + "`" +
+ (e.message ? ': ' + e.message : ''),
+ index: this.index, filename: this.currentFileInfo.filename };
+ }
+ }
+
+ return new tree.Call(this.name, args, this.index, this.currentFileInfo);
+ },
+
+ genCSS: function (env, output) {
+ output.add(this.name + "(", this.currentFileInfo, this.index);
+
+ for(var i = 0; i < this.args.length; i++) {
+ this.args[i].genCSS(env, output);
+ if (i + 1 < this.args.length) {
+ output.add(", ");
+ }
+ }
+
+ output.add(")");
+ },
+
+ toCSS: tree.toCSS
+};
+
+})(require('../tree'));
+
+(function (tree) {
+//
+// RGB Colors - #ff0014, #eee
+//
+tree.Color = function (rgb, a) {
+ //
+ // The end goal here, is to parse the arguments
+ // into an integer triplet, such as `128, 255, 0`
+ //
+ // This facilitates operations and conversions.
+ //
+ if (Array.isArray(rgb)) {
+ this.rgb = rgb;
+ } else if (rgb.length == 6) {
+ this.rgb = rgb.match(/.{2}/g).map(function (c) {
+ return parseInt(c, 16);
+ });
+ } else {
+ this.rgb = rgb.split('').map(function (c) {
+ return parseInt(c + c, 16);
+ });
+ }
+ this.alpha = typeof(a) === 'number' ? a : 1;
+};
+
+var transparentKeyword = "transparent";
+
+tree.Color.prototype = {
+ type: "Color",
+ eval: function () { return this; },
+ luma: function () { return (0.2126 * this.rgb[0] / 255) + (0.7152 * this.rgb[1] / 255) + (0.0722 * this.rgb[2] / 255); },
+
+ genCSS: function (env, output) {
+ output.add(this.toCSS(env));
+ },
+ toCSS: function (env, doNotCompress) {
+ var compress = env && env.compress && !doNotCompress;
+
+ // If we have some transparency, the only way to represent it
+ // is via `rgba`. Otherwise, we use the hex representation,
+ // which has better compatibility with older browsers.
+ // Values are capped between `0` and `255`, rounded and zero-padded.
+ if (this.alpha < 1.0) {
+ if (this.alpha === 0 && this.isTransparentKeyword) {
+ return transparentKeyword;
+ }
+ return "rgba(" + this.rgb.map(function (c) {
+ return Math.round(c);
+ }).concat(this.alpha).join(',' + (compress ? '' : ' ')) + ")";
+ } else {
+ var color = this.toRGB();
+
+ if (compress) {
+ var splitcolor = color.split('');
+
+ // Convert color to short format
+ if (splitcolor[1] === splitcolor[2] && splitcolor[3] === splitcolor[4] && splitcolor[5] === splitcolor[6]) {
+ color = '#' + splitcolor[1] + splitcolor[3] + splitcolor[5];
+ }
+ }
+
+ return color;
+ }
+ },
+
+ //
+ // Operations have to be done per-channel, if not,
+ // channels will spill onto each other. Once we have
+ // our result, in the form of an integer triplet,
+ // we create a new Color node to hold the result.
+ //
+ operate: function (env, op, other) {
+ var result = [];
+
+ if (! (other instanceof tree.Color)) {
+ other = other.toColor();
+ }
+
+ for (var c = 0; c < 3; c++) {
+ result[c] = tree.operate(env, op, this.rgb[c], other.rgb[c]);
+ }
+ return new(tree.Color)(result, this.alpha + other.alpha);
+ },
+
+ toRGB: function () {
+ return '#' + this.rgb.map(function (i) {
+ i = Math.round(i);
+ i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16);
+ return i.length === 1 ? '0' + i : i;
+ }).join('');
+ },
+
+ toHSL: function () {
+ var r = this.rgb[0] / 255,
+ g = this.rgb[1] / 255,
+ b = this.rgb[2] / 255,
+ a = this.alpha;
+
+ var max = Math.max(r, g, b), min = Math.min(r, g, b);
+ var h, s, l = (max + min) / 2, d = max - min;
+
+ if (max === min) {
+ h = s = 0;
+ } else {
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+
+ switch (max) {
+ case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+ case g: h = (b - r) / d + 2; break;
+ case b: h = (r - g) / d + 4; break;
+ }
+ h /= 6;
+ }
+ return { h: h * 360, s: s, l: l, a: a };
+ },
+ //Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
+ toHSV: function () {
+ var r = this.rgb[0] / 255,
+ g = this.rgb[1] / 255,
+ b = this.rgb[2] / 255,
+ a = this.alpha;
+
+ var max = Math.max(r, g, b), min = Math.min(r, g, b);
+ var h, s, v = max;
+
+ var d = max - min;
+ if (max === 0) {
+ s = 0;
+ } else {
+ s = d / max;
+ }
+
+ if (max === min) {
+ h = 0;
+ } else {
+ switch(max){
+ case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+ case g: h = (b - r) / d + 2; break;
+ case b: h = (r - g) / d + 4; break;
+ }
+ h /= 6;
+ }
+ return { h: h * 360, s: s, v: v, a: a };
+ },
+ toARGB: function () {
+ var argb = [Math.round(this.alpha * 255)].concat(this.rgb);
+ return '#' + argb.map(function (i) {
+ i = Math.round(i);
+ i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16);
+ return i.length === 1 ? '0' + i : i;
+ }).join('');
+ },
+ compare: function (x) {
+ if (!x.rgb) {
+ return -1;
+ }
+
+ return (x.rgb[0] === this.rgb[0] &&
+ x.rgb[1] === this.rgb[1] &&
+ x.rgb[2] === this.rgb[2] &&
+ x.alpha === this.alpha) ? 0 : -1;
+ }
+};
+
+tree.Color.fromKeyword = function(keyword) {
+ if (tree.colors.hasOwnProperty(keyword)) {
+ // detect named color
+ return new(tree.Color)(tree.colors[keyword].slice(1));
+ }
+ if (keyword === transparentKeyword) {
+ var transparent = new(tree.Color)([0, 0, 0], 0);
+ transparent.isTransparentKeyword = true;
+ return transparent;
+ }
+};
+
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Comment = function (value, silent, index, currentFileInfo) {
+ this.value = value;
+ this.silent = !!silent;
+ this.currentFileInfo = currentFileInfo;
+};
+tree.Comment.prototype = {
+ type: "Comment",
+ genCSS: function (env, output) {
+ if (this.debugInfo) {
+ output.add(tree.debugInfo(env, this), this.currentFileInfo, this.index);
+ }
+ output.add(this.value.trim()); //TODO shouldn't need to trim, we shouldn't grab the \n
+ },
+ toCSS: tree.toCSS,
+ isSilent: function(env) {
+ var isReference = (this.currentFileInfo && this.currentFileInfo.reference && !this.isReferenced),
+ isCompressed = env.compress && !this.value.match(/^\/\*!/);
+ return this.silent || isReference || isCompressed;
+ },
+ eval: function () { return this; },
+ markReferenced: function () {
+ this.isReferenced = true;
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Condition = function (op, l, r, i, negate) {
+ this.op = op.trim();
+ this.lvalue = l;
+ this.rvalue = r;
+ this.index = i;
+ this.negate = negate;
+};
+tree.Condition.prototype = {
+ type: "Condition",
+ accept: function (visitor) {
+ this.lvalue = visitor.visit(this.lvalue);
+ this.rvalue = visitor.visit(this.rvalue);
+ },
+ eval: function (env) {
+ var a = this.lvalue.eval(env),
+ b = this.rvalue.eval(env);
+
+ var i = this.index, result;
+
+ result = (function (op) {
+ switch (op) {
+ case 'and':
+ return a && b;
+ case 'or':
+ return a || b;
+ default:
+ if (a.compare) {
+ result = a.compare(b);
+ } else if (b.compare) {
+ result = b.compare(a);
+ } else {
+ throw { type: "Type",
+ message: "Unable to perform comparison",
+ index: i };
+ }
+ switch (result) {
+ case -1: return op === '<' || op === '=<' || op === '<=';
+ case 0: return op === '=' || op === '>=' || op === '=<' || op === '<=';
+ case 1: return op === '>' || op === '>=';
+ }
+ }
+ })(this.op);
+ return this.negate ? !result : result;
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+//
+// A number with a unit
+//
+tree.Dimension = function (value, unit) {
+ this.value = parseFloat(value);
+ this.unit = (unit && unit instanceof tree.Unit) ? unit :
+ new(tree.Unit)(unit ? [unit] : undefined);
+};
+
+tree.Dimension.prototype = {
+ type: "Dimension",
+ accept: function (visitor) {
+ this.unit = visitor.visit(this.unit);
+ },
+ eval: function (env) {
+ return this;
+ },
+ toColor: function () {
+ return new(tree.Color)([this.value, this.value, this.value]);
+ },
+ genCSS: function (env, output) {
+ if ((env && env.strictUnits) && !this.unit.isSingular()) {
+ throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: "+this.unit.toString());
+ }
+
+ var value = this.value,
+ strValue = String(value);
+
+ if (value !== 0 && value < 0.000001 && value > -0.000001) {
+ // would be output 1e-6 etc.
+ strValue = value.toFixed(20).replace(/0+$/, "");
+ }
+
+ if (env && env.compress) {
+ // Zero values doesn't need a unit
+ if (value === 0 && this.unit.isLength()) {
+ output.add(strValue);
+ return;
+ }
+
+ // Float values doesn't need a leading zero
+ if (value > 0 && value < 1) {
+ strValue = (strValue).substr(1);
+ }
+ }
+
+ output.add(strValue);
+ this.unit.genCSS(env, output);
+ },
+ toCSS: tree.toCSS,
+
+ // In an operation between two Dimensions,
+ // we default to the first Dimension's unit,
+ // so `1px + 2` will yield `3px`.
+ operate: function (env, op, other) {
+ /*jshint noempty:false */
+ var value = tree.operate(env, op, this.value, other.value),
+ unit = this.unit.clone();
+
+ if (op === '+' || op === '-') {
+ if (unit.numerator.length === 0 && unit.denominator.length === 0) {
+ unit.numerator = other.unit.numerator.slice(0);
+ unit.denominator = other.unit.denominator.slice(0);
+ } else if (other.unit.numerator.length === 0 && unit.denominator.length === 0) {
+ // do nothing
+ } else {
+ other = other.convertTo(this.unit.usedUnits());
+
+ if(env.strictUnits && other.unit.toString() !== unit.toString()) {
+ throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '" + unit.toString() +
+ "' and '" + other.unit.toString() + "'.");
+ }
+
+ value = tree.operate(env, op, this.value, other.value);
+ }
+ } else if (op === '*') {
+ unit.numerator = unit.numerator.concat(other.unit.numerator).sort();
+ unit.denominator = unit.denominator.concat(other.unit.denominator).sort();
+ unit.cancel();
+ } else if (op === '/') {
+ unit.numerator = unit.numerator.concat(other.unit.denominator).sort();
+ unit.denominator = unit.denominator.concat(other.unit.numerator).sort();
+ unit.cancel();
+ }
+ return new(tree.Dimension)(value, unit);
+ },
+
+ compare: function (other) {
+ if (other instanceof tree.Dimension) {
+ var a = this.unify(), b = other.unify(),
+ aValue = a.value, bValue = b.value;
+
+ if (bValue > aValue) {
+ return -1;
+ } else if (bValue < aValue) {
+ return 1;
+ } else {
+ if (!b.unit.isEmpty() && a.unit.compare(b.unit) !== 0) {
+ return -1;
+ }
+ return 0;
+ }
+ } else {
+ return -1;
+ }
+ },
+
+ unify: function () {
+ return this.convertTo({ length: 'm', duration: 's', angle: 'rad' });
+ },
+
+ convertTo: function (conversions) {
+ var value = this.value, unit = this.unit.clone(),
+ i, groupName, group, targetUnit, derivedConversions = {}, applyUnit;
+
+ if (typeof conversions === 'string') {
+ for(i in tree.UnitConversions) {
+ if (tree.UnitConversions[i].hasOwnProperty(conversions)) {
+ derivedConversions = {};
+ derivedConversions[i] = conversions;
+ }
+ }
+ conversions = derivedConversions;
+ }
+ applyUnit = function (atomicUnit, denominator) {
+ /*jshint loopfunc:true */
+ if (group.hasOwnProperty(atomicUnit)) {
+ if (denominator) {
+ value = value / (group[atomicUnit] / group[targetUnit]);
+ } else {
+ value = value * (group[atomicUnit] / group[targetUnit]);
+ }
+
+ return targetUnit;
+ }
+
+ return atomicUnit;
+ };
+
+ for (groupName in conversions) {
+ if (conversions.hasOwnProperty(groupName)) {
+ targetUnit = conversions[groupName];
+ group = tree.UnitConversions[groupName];
+
+ unit.map(applyUnit);
+ }
+ }
+
+ unit.cancel();
+
+ return new(tree.Dimension)(value, unit);
+ }
+};
+
+// http://www.w3.org/TR/css3-values/#absolute-lengths
+tree.UnitConversions = {
+ length: {
+ 'm': 1,
+ 'cm': 0.01,
+ 'mm': 0.001,
+ 'in': 0.0254,
+ 'pt': 0.0254 / 72,
+ 'pc': 0.0254 / 72 * 12
+ },
+ duration: {
+ 's': 1,
+ 'ms': 0.001
+ },
+ angle: {
+ 'rad': 1/(2*Math.PI),
+ 'deg': 1/360,
+ 'grad': 1/400,
+ 'turn': 1
+ }
+};
+
+tree.Unit = function (numerator, denominator, backupUnit) {
+ this.numerator = numerator ? numerator.slice(0).sort() : [];
+ this.denominator = denominator ? denominator.slice(0).sort() : [];
+ this.backupUnit = backupUnit;
+};
+
+tree.Unit.prototype = {
+ type: "Unit",
+ clone: function () {
+ return new tree.Unit(this.numerator.slice(0), this.denominator.slice(0), this.backupUnit);
+ },
+ genCSS: function (env, output) {
+ if (this.numerator.length >= 1) {
+ output.add(this.numerator[0]);
+ } else
+ if (this.denominator.length >= 1) {
+ output.add(this.denominator[0]);
+ } else
+ if ((!env || !env.strictUnits) && this.backupUnit) {
+ output.add(this.backupUnit);
+ }
+ },
+ toCSS: tree.toCSS,
+
+ toString: function () {
+ var i, returnStr = this.numerator.join("*");
+ for (i = 0; i < this.denominator.length; i++) {
+ returnStr += "/" + this.denominator[i];
+ }
+ return returnStr;
+ },
+
+ compare: function (other) {
+ return this.is(other.toString()) ? 0 : -1;
+ },
+
+ is: function (unitString) {
+ return this.toString() === unitString;
+ },
+
+ isLength: function () {
+ return Boolean(this.toCSS().match(/px|em|%|in|cm|mm|pc|pt|ex/));
+ },
+
+ isEmpty: function () {
+ return this.numerator.length === 0 && this.denominator.length === 0;
+ },
+
+ isSingular: function() {
+ return this.numerator.length <= 1 && this.denominator.length === 0;
+ },
+
+ map: function(callback) {
+ var i;
+
+ for (i = 0; i < this.numerator.length; i++) {
+ this.numerator[i] = callback(this.numerator[i], false);
+ }
+
+ for (i = 0; i < this.denominator.length; i++) {
+ this.denominator[i] = callback(this.denominator[i], true);
+ }
+ },
+
+ usedUnits: function() {
+ var group, result = {}, mapUnit;
+
+ mapUnit = function (atomicUnit) {
+ /*jshint loopfunc:true */
+ if (group.hasOwnProperty(atomicUnit) && !result[groupName]) {
+ result[groupName] = atomicUnit;
+ }
+
+ return atomicUnit;
+ };
+
+ for (var groupName in tree.UnitConversions) {
+ if (tree.UnitConversions.hasOwnProperty(groupName)) {
+ group = tree.UnitConversions[groupName];
+
+ this.map(mapUnit);
+ }
+ }
+
+ return result;
+ },
+
+ cancel: function () {
+ var counter = {}, atomicUnit, i, backup;
+
+ for (i = 0; i < this.numerator.length; i++) {
+ atomicUnit = this.numerator[i];
+ if (!backup) {
+ backup = atomicUnit;
+ }
+ counter[atomicUnit] = (counter[atomicUnit] || 0) + 1;
+ }
+
+ for (i = 0; i < this.denominator.length; i++) {
+ atomicUnit = this.denominator[i];
+ if (!backup) {
+ backup = atomicUnit;
+ }
+ counter[atomicUnit] = (counter[atomicUnit] || 0) - 1;
+ }
+
+ this.numerator = [];
+ this.denominator = [];
+
+ for (atomicUnit in counter) {
+ if (counter.hasOwnProperty(atomicUnit)) {
+ var count = counter[atomicUnit];
+
+ if (count > 0) {
+ for (i = 0; i < count; i++) {
+ this.numerator.push(atomicUnit);
+ }
+ } else if (count < 0) {
+ for (i = 0; i < -count; i++) {
+ this.denominator.push(atomicUnit);
+ }
+ }
+ }
+ }
+
+ if (this.numerator.length === 0 && this.denominator.length === 0 && backup) {
+ this.backupUnit = backup;
+ }
+
+ this.numerator.sort();
+ this.denominator.sort();
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Directive = function (name, value, index, currentFileInfo) {
+ this.name = name;
+
+ if (Array.isArray(value)) {
+ this.rules = [new(tree.Ruleset)([], value)];
+ this.rules[0].allowImports = true;
+ } else {
+ this.value = value;
+ }
+ this.currentFileInfo = currentFileInfo;
+
+};
+tree.Directive.prototype = {
+ type: "Directive",
+ accept: function (visitor) {
+ this.rules = visitor.visit(this.rules);
+ this.value = visitor.visit(this.value);
+ },
+ genCSS: function (env, output) {
+ output.add(this.name, this.currentFileInfo, this.index);
+ if (this.rules) {
+ tree.outputRuleset(env, output, this.rules);
+ } else {
+ output.add(' ');
+ this.value.genCSS(env, output);
+ output.add(';');
+ }
+ },
+ toCSS: tree.toCSS,
+ eval: function (env) {
+ var evaldDirective = this;
+ if (this.rules) {
+ env.frames.unshift(this);
+ evaldDirective = new(tree.Directive)(this.name, null, this.index, this.currentFileInfo);
+ evaldDirective.rules = [this.rules[0].eval(env)];
+ evaldDirective.rules[0].root = true;
+ env.frames.shift();
+ }
+ return evaldDirective;
+ },
+ variable: function (name) { return tree.Ruleset.prototype.variable.call(this.rules[0], name); },
+ find: function () { return tree.Ruleset.prototype.find.apply(this.rules[0], arguments); },
+ rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.rules[0]); },
+ markReferenced: function () {
+ var i, rules;
+ this.isReferenced = true;
+ if (this.rules) {
+ rules = this.rules[0].rules;
+ for (i = 0; i < rules.length; i++) {
+ if (rules[i].markReferenced) {
+ rules[i].markReferenced();
+ }
+ }
+ }
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Element = function (combinator, value, index, currentFileInfo) {
+ this.combinator = combinator instanceof tree.Combinator ?
+ combinator : new(tree.Combinator)(combinator);
+
+ if (typeof(value) === 'string') {
+ this.value = value.trim();
+ } else if (value) {
+ this.value = value;
+ } else {
+ this.value = "";
+ }
+ this.index = index;
+ this.currentFileInfo = currentFileInfo;
+};
+tree.Element.prototype = {
+ type: "Element",
+ accept: function (visitor) {
+ this.combinator = visitor.visit(this.combinator);
+ this.value = visitor.visit(this.value);
+ },
+ eval: function (env) {
+ return new(tree.Element)(this.combinator,
+ this.value.eval ? this.value.eval(env) : this.value,
+ this.index,
+ this.currentFileInfo);
+ },
+ genCSS: function (env, output) {
+ output.add(this.toCSS(env), this.currentFileInfo, this.index);
+ },
+ toCSS: function (env) {
+ var value = (this.value.toCSS ? this.value.toCSS(env) : this.value);
+ if (value === '' && this.combinator.value.charAt(0) === '&') {
+ return '';
+ } else {
+ return this.combinator.toCSS(env || {}) + value;
+ }
+ }
+};
+
+tree.Attribute = function (key, op, value) {
+ this.key = key;
+ this.op = op;
+ this.value = value;
+};
+tree.Attribute.prototype = {
+ type: "Attribute",
+ accept: function (visitor) {
+ this.value = visitor.visit(this.value);
+ },
+ eval: function (env) {
+ return new(tree.Attribute)(this.key.eval ? this.key.eval(env) : this.key,
+ this.op, (this.value && this.value.eval) ? this.value.eval(env) : this.value);
+ },
+ genCSS: function (env, output) {
+ output.add(this.toCSS(env));
+ },
+ toCSS: function (env) {
+ var value = this.key.toCSS ? this.key.toCSS(env) : this.key;
+
+ if (this.op) {
+ value += this.op;
+ value += (this.value.toCSS ? this.value.toCSS(env) : this.value);
+ }
+
+ return '[' + value + ']';
+ }
+};
+
+tree.Combinator = function (value) {
+ if (value === ' ') {
+ this.value = ' ';
+ } else {
+ this.value = value ? value.trim() : "";
+ }
+};
+tree.Combinator.prototype = {
+ type: "Combinator",
+ _outputMap: {
+ '' : '',
+ ' ' : ' ',
+ ':' : ' :',
+ '+' : ' + ',
+ '~' : ' ~ ',
+ '>' : ' > ',
+ '|' : '|'
+ },
+ _outputMapCompressed: {
+ '' : '',
+ ' ' : ' ',
+ ':' : ' :',
+ '+' : '+',
+ '~' : '~',
+ '>' : '>',
+ '|' : '|'
+ },
+ genCSS: function (env, output) {
+ output.add((env.compress ? this._outputMapCompressed : this._outputMap)[this.value]);
+ },
+ toCSS: tree.toCSS
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Expression = function (value) { this.value = value; };
+tree.Expression.prototype = {
+ type: "Expression",
+ accept: function (visitor) {
+ this.value = visitor.visit(this.value);
+ },
+ eval: function (env) {
+ var returnValue,
+ inParenthesis = this.parens && !this.parensInOp,
+ doubleParen = false;
+ if (inParenthesis) {
+ env.inParenthesis();
+ }
+ if (this.value.length > 1) {
+ returnValue = new(tree.Expression)(this.value.map(function (e) {
+ return e.eval(env);
+ }));
+ } else if (this.value.length === 1) {
+ if (this.value[0].parens && !this.value[0].parensInOp) {
+ doubleParen = true;
+ }
+ returnValue = this.value[0].eval(env);
+ } else {
+ returnValue = this;
+ }
+ if (inParenthesis) {
+ env.outOfParenthesis();
+ }
+ if (this.parens && this.parensInOp && !(env.isMathOn()) && !doubleParen) {
+ returnValue = new(tree.Paren)(returnValue);
+ }
+ return returnValue;
+ },
+ genCSS: function (env, output) {
+ for(var i = 0; i < this.value.length; i++) {
+ this.value[i].genCSS(env, output);
+ if (i + 1 < this.value.length) {
+ output.add(" ");
+ }
+ }
+ },
+ toCSS: tree.toCSS,
+ throwAwayComments: function () {
+ this.value = this.value.filter(function(v) {
+ return !(v instanceof tree.Comment);
+ });
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Extend = function Extend(selector, option, index) {
+ this.selector = selector;
+ this.option = option;
+ this.index = index;
+
+ switch(option) {
+ case "all":
+ this.allowBefore = true;
+ this.allowAfter = true;
+ break;
+ default:
+ this.allowBefore = false;
+ this.allowAfter = false;
+ break;
+ }
+};
+
+tree.Extend.prototype = {
+ type: "Extend",
+ accept: function (visitor) {
+ this.selector = visitor.visit(this.selector);
+ },
+ eval: function (env) {
+ return new(tree.Extend)(this.selector.eval(env), this.option, this.index);
+ },
+ clone: function (env) {
+ return new(tree.Extend)(this.selector, this.option, this.index);
+ },
+ findSelfSelectors: function (selectors) {
+ var selfElements = [],
+ i,
+ selectorElements;
+
+ for(i = 0; i < selectors.length; i++) {
+ selectorElements = selectors[i].elements;
+ // duplicate the logic in genCSS function inside the selector node.
+ // future TODO - move both logics into the selector joiner visitor
+ if (i > 0 && selectorElements.length && selectorElements[0].combinator.value === "") {
+ selectorElements[0].combinator.value = ' ';
+ }
+ selfElements = selfElements.concat(selectors[i].elements);
+ }
+
+ this.selfSelectors = [{ elements: selfElements }];
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+//
+// CSS @import node
+//
+// The general strategy here is that we don't want to wait
+// for the parsing to be completed, before we start importing
+// the file. That's because in the context of a browser,
+// most of the time will be spent waiting for the server to respond.
+//
+// On creation, we push the import path to our import queue, though
+// `import,push`, we also pass it a callback, which it'll call once
+// the file has been fetched, and parsed.
+//
+tree.Import = function (path, features, options, index, currentFileInfo) {
+ this.options = options;
+ this.index = index;
+ this.path = path;
+ this.features = features;
+ this.currentFileInfo = currentFileInfo;
+
+ if (this.options.less !== undefined || this.options.inline) {
+ this.css = !this.options.less || this.options.inline;
+ } else {
+ var pathValue = this.getPath();
+ if (pathValue && /css([\?;].*)?$/.test(pathValue)) {
+ this.css = true;
+ }
+ }
+};
+
+//
+// The actual import node doesn't return anything, when converted to CSS.
+// The reason is that it's used at the evaluation stage, so that the rules
+// it imports can be treated like any other rules.
+//
+// In `eval`, we make sure all Import nodes get evaluated, recursively, so
+// we end up with a flat structure, which can easily be imported in the parent
+// ruleset.
+//
+tree.Import.prototype = {
+ type: "Import",
+ accept: function (visitor) {
+ this.features = visitor.visit(this.features);
+ this.path = visitor.visit(this.path);
+ if (!this.options.inline) {
+ this.root = visitor.visit(this.root);
+ }
+ },
+ genCSS: function (env, output) {
+ if (this.css) {
+ output.add("@import ", this.currentFileInfo, this.index);
+ this.path.genCSS(env, output);
+ if (this.features) {
+ output.add(" ");
+ this.features.genCSS(env, output);
+ }
+ output.add(';');
+ }
+ },
+ toCSS: tree.toCSS,
+ getPath: function () {
+ if (this.path instanceof tree.Quoted) {
+ var path = this.path.value;
+ return (this.css !== undefined || /(\.[a-z]*$)|([\?;].*)$/.test(path)) ? path : path + '.less';
+ } else if (this.path instanceof tree.URL) {
+ return this.path.value.value;
+ }
+ return null;
+ },
+ evalForImport: function (env) {
+ return new(tree.Import)(this.path.eval(env), this.features, this.options, this.index, this.currentFileInfo);
+ },
+ evalPath: function (env) {
+ var path = this.path.eval(env);
+ var rootpath = this.currentFileInfo && this.currentFileInfo.rootpath;
+
+ if (!(path instanceof tree.URL)) {
+ if (rootpath) {
+ var pathValue = path.value;
+ // Add the base path if the import is relative
+ if (pathValue && env.isPathRelative(pathValue)) {
+ path.value = rootpath + pathValue;
+ }
+ }
+ path.value = env.normalizePath(path.value);
+ }
+
+ return path;
+ },
+ eval: function (env) {
+ var ruleset, features = this.features && this.features.eval(env);
+
+ if (this.skip) { return []; }
+
+ if (this.options.inline) {
+ //todo needs to reference css file not import
+ var contents = new(tree.Anonymous)(this.root, 0, {filename: this.importedFilename}, true);
+ return this.features ? new(tree.Media)([contents], this.features.value) : [contents];
+ } else if (this.css) {
+ var newImport = new(tree.Import)(this.evalPath(env), features, this.options, this.index);
+ if (!newImport.css && this.error) {
+ throw this.error;
+ }
+ return newImport;
+ } else {
+ ruleset = new(tree.Ruleset)([], this.root.rules.slice(0));
+
+ ruleset.evalImports(env);
+
+ return this.features ? new(tree.Media)(ruleset.rules, this.features.value) : ruleset.rules;
+ }
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.JavaScript = function (string, index, escaped) {
+ this.escaped = escaped;
+ this.expression = string;
+ this.index = index;
+};
+tree.JavaScript.prototype = {
+ type: "JavaScript",
+ eval: function (env) {
+ var result,
+ that = this,
+ context = {};
+
+ var expression = this.expression.replace(/@\{([\w-]+)\}/g, function (_, name) {
+ return tree.jsify(new(tree.Variable)('@' + name, that.index).eval(env));
+ });
+
+ try {
+ expression = new(Function)('return (' + expression + ')');
+ } catch (e) {
+ throw { message: "JavaScript evaluation error: " + e.message + " from `" + expression + "`" ,
+ index: this.index };
+ }
+
+ for (var k in env.frames[0].variables()) {
+ /*jshint loopfunc:true */
+ context[k.slice(1)] = {
+ value: env.frames[0].variables()[k].value,
+ toJS: function () {
+ return this.value.eval(env).toCSS();
+ }
+ };
+ }
+
+ try {
+ result = expression.call(context);
+ } catch (e) {
+ throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message + "'" ,
+ index: this.index };
+ }
+ if (typeof(result) === 'string') {
+ return new(tree.Quoted)('"' + result + '"', result, this.escaped, this.index);
+ } else if (Array.isArray(result)) {
+ return new(tree.Anonymous)(result.join(', '));
+ } else {
+ return new(tree.Anonymous)(result);
+ }
+ }
+};
+
+})(require('../tree'));
+
+
+(function (tree) {
+
+tree.Keyword = function (value) { this.value = value; };
+tree.Keyword.prototype = {
+ type: "Keyword",
+ eval: function () { return this; },
+ genCSS: function (env, output) {
+ output.add(this.value);
+ },
+ toCSS: tree.toCSS,
+ compare: function (other) {
+ if (other instanceof tree.Keyword) {
+ return other.value === this.value ? 0 : 1;
+ } else {
+ return -1;
+ }
+ }
+};
+
+tree.True = new(tree.Keyword)('true');
+tree.False = new(tree.Keyword)('false');
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Media = function (value, features, index, currentFileInfo) {
+ this.index = index;
+ this.currentFileInfo = currentFileInfo;
+
+ var selectors = this.emptySelectors();
+
+ this.features = new(tree.Value)(features);
+ this.rules = [new(tree.Ruleset)(selectors, value)];
+ this.rules[0].allowImports = true;
+};
+tree.Media.prototype = {
+ type: "Media",
+ accept: function (visitor) {
+ this.features = visitor.visit(this.features);
+ this.rules = visitor.visit(this.rules);
+ },
+ genCSS: function (env, output) {
+ output.add('@media ', this.currentFileInfo, this.index);
+ this.features.genCSS(env, output);
+ tree.outputRuleset(env, output, this.rules);
+ },
+ toCSS: tree.toCSS,
+ eval: function (env) {
+ if (!env.mediaBlocks) {
+ env.mediaBlocks = [];
+ env.mediaPath = [];
+ }
+
+ var media = new(tree.Media)([], [], this.index, this.currentFileInfo);
+ if(this.debugInfo) {
+ this.rules[0].debugInfo = this.debugInfo;
+ media.debugInfo = this.debugInfo;
+ }
+ var strictMathBypass = false;
+ if (!env.strictMath) {
+ strictMathBypass = true;
+ env.strictMath = true;
+ }
+ try {
+ media.features = this.features.eval(env);
+ }
+ finally {
+ if (strictMathBypass) {
+ env.strictMath = false;
+ }
+ }
+
+ env.mediaPath.push(media);
+ env.mediaBlocks.push(media);
+
+ env.frames.unshift(this.rules[0]);
+ media.rules = [this.rules[0].eval(env)];
+ env.frames.shift();
+
+ env.mediaPath.pop();
+
+ return env.mediaPath.length === 0 ? media.evalTop(env) :
+ media.evalNested(env);
+ },
+ variable: function (name) { return tree.Ruleset.prototype.variable.call(this.rules[0], name); },
+ find: function () { return tree.Ruleset.prototype.find.apply(this.rules[0], arguments); },
+ rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.rules[0]); },
+ emptySelectors: function() {
+ var el = new(tree.Element)('', '&', this.index, this.currentFileInfo);
+ return [new(tree.Selector)([el], null, null, this.index, this.currentFileInfo)];
+ },
+ markReferenced: function () {
+ var i, rules = this.rules[0].rules;
+ this.isReferenced = true;
+ for (i = 0; i < rules.length; i++) {
+ if (rules[i].markReferenced) {
+ rules[i].markReferenced();
+ }
+ }
+ },
+
+ evalTop: function (env) {
+ var result = this;
+
+ // Render all dependent Media blocks.
+ if (env.mediaBlocks.length > 1) {
+ var selectors = this.emptySelectors();
+ result = new(tree.Ruleset)(selectors, env.mediaBlocks);
+ result.multiMedia = true;
+ }
+
+ delete env.mediaBlocks;
+ delete env.mediaPath;
+
+ return result;
+ },
+ evalNested: function (env) {
+ var i, value,
+ path = env.mediaPath.concat([this]);
+
+ // Extract the media-query conditions separated with `,` (OR).
+ for (i = 0; i < path.length; i++) {
+ value = path[i].features instanceof tree.Value ?
+ path[i].features.value : path[i].features;
+ path[i] = Array.isArray(value) ? value : [value];
+ }
+
+ // Trace all permutations to generate the resulting media-query.
+ //
+ // (a, b and c) with nested (d, e) ->
+ // a and d
+ // a and e
+ // b and c and d
+ // b and c and e
+ this.features = new(tree.Value)(this.permute(path).map(function (path) {
+ path = path.map(function (fragment) {
+ return fragment.toCSS ? fragment : new(tree.Anonymous)(fragment);
+ });
+
+ for(i = path.length - 1; i > 0; i--) {
+ path.splice(i, 0, new(tree.Anonymous)("and"));
+ }
+
+ return new(tree.Expression)(path);
+ }));
+
+ // Fake a tree-node that doesn't output anything.
+ return new(tree.Ruleset)([], []);
+ },
+ permute: function (arr) {
+ if (arr.length === 0) {
+ return [];
+ } else if (arr.length === 1) {
+ return arr[0];
+ } else {
+ var result = [];
+ var rest = this.permute(arr.slice(1));
+ for (var i = 0; i < rest.length; i++) {
+ for (var j = 0; j < arr[0].length; j++) {
+ result.push([arr[0][j]].concat(rest[i]));
+ }
+ }
+ return result;
+ }
+ },
+ bubbleSelectors: function (selectors) {
+ this.rules = [new(tree.Ruleset)(selectors.slice(0), [this.rules[0]])];
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.mixin = {};
+tree.mixin.Call = function (elements, args, index, currentFileInfo, important) {
+ this.selector = new(tree.Selector)(elements);
+ this.arguments = args;
+ this.index = index;
+ this.currentFileInfo = currentFileInfo;
+ this.important = important;
+};
+tree.mixin.Call.prototype = {
+ type: "MixinCall",
+ accept: function (visitor) {
+ this.selector = visitor.visit(this.selector);
+ this.arguments = visitor.visit(this.arguments);
+ },
+ eval: function (env) {
+ var mixins, mixin, args, rules = [], match = false, i, m, f, isRecursive, isOneFound, rule;
+
+ args = this.arguments && this.arguments.map(function (a) {
+ return { name: a.name, value: a.value.eval(env) };
+ });
+
+ for (i = 0; i < env.frames.length; i++) {
+ if ((mixins = env.frames[i].find(this.selector)).length > 0) {
+ isOneFound = true;
+ for (m = 0; m < mixins.length; m++) {
+ mixin = mixins[m];
+ isRecursive = false;
+ for(f = 0; f < env.frames.length; f++) {
+ if ((!(mixin instanceof tree.mixin.Definition)) && mixin === (env.frames[f].originalRuleset || env.frames[f])) {
+ isRecursive = true;
+ break;
+ }
+ }
+ if (isRecursive) {
+ continue;
+ }
+ if (mixin.matchArgs(args, env)) {
+ if (!mixin.matchCondition || mixin.matchCondition(args, env)) {
+ try {
+ if (!(mixin instanceof tree.mixin.Definition)) {
+ mixin = new tree.mixin.Definition("", [], mixin.rules, null, false);
+ mixin.originalRuleset = mixins[m].originalRuleset || mixins[m];
+ }
+ //if (this.important) {
+ // isImportant = env.isImportant;
+ // env.isImportant = true;
+ //}
+ Array.prototype.push.apply(
+ rules, mixin.eval(env, args, this.important).rules);
+ //if (this.important) {
+ // env.isImportant = isImportant;
+ //}
+ } catch (e) {
+ throw { message: e.message, index: this.index, filename: this.currentFileInfo.filename, stack: e.stack };
+ }
+ }
+ match = true;
+ }
+ }
+ if (match) {
+ if (!this.currentFileInfo || !this.currentFileInfo.reference) {
+ for (i = 0; i < rules.length; i++) {
+ rule = rules[i];
+ if (rule.markReferenced) {
+ rule.markReferenced();
+ }
+ }
+ }
+ return rules;
+ }
+ }
+ }
+ if (isOneFound) {
+ throw { type: 'Runtime',
+ message: 'No matching definition was found for `' +
+ this.selector.toCSS().trim() + '(' +
+ (args ? args.map(function (a) {
+ var argValue = "";
+ if (a.name) {
+ argValue += a.name + ":";
+ }
+ if (a.value.toCSS) {
+ argValue += a.value.toCSS();
+ } else {
+ argValue += "???";
+ }
+ return argValue;
+ }).join(', ') : "") + ")`",
+ index: this.index, filename: this.currentFileInfo.filename };
+ } else {
+ throw { type: 'Name',
+ message: this.selector.toCSS().trim() + " is undefined",
+ index: this.index, filename: this.currentFileInfo.filename };
+ }
+ }
+};
+
+tree.mixin.Definition = function (name, params, rules, condition, variadic) {
+ this.name = name;
+ this.selectors = [new(tree.Selector)([new(tree.Element)(null, name, this.index, this.currentFileInfo)])];
+ this.params = params;
+ this.condition = condition;
+ this.variadic = variadic;
+ this.arity = params.length;
+ this.rules = rules;
+ this._lookups = {};
+ this.required = params.reduce(function (count, p) {
+ if (!p.name || (p.name && !p.value)) { return count + 1; }
+ else { return count; }
+ }, 0);
+ this.parent = tree.Ruleset.prototype;
+ this.frames = [];
+};
+tree.mixin.Definition.prototype = {
+ type: "MixinDefinition",
+ accept: function (visitor) {
+ this.params = visitor.visit(this.params);
+ this.rules = visitor.visit(this.rules);
+ this.condition = visitor.visit(this.condition);
+ },
+ variable: function (name) { return this.parent.variable.call(this, name); },
+ variables: function () { return this.parent.variables.call(this); },
+ find: function () { return this.parent.find.apply(this, arguments); },
+ rulesets: function () { return this.parent.rulesets.apply(this); },
+
+ evalParams: function (env, mixinEnv, args, evaldArguments) {
+ /*jshint boss:true */
+ var frame = new(tree.Ruleset)(null, []),
+ varargs, arg,
+ params = this.params.slice(0),
+ i, j, val, name, isNamedFound, argIndex;
+
+ mixinEnv = new tree.evalEnv(mixinEnv, [frame].concat(mixinEnv.frames));
+
+ if (args) {
+ args = args.slice(0);
+
+ for(i = 0; i < args.length; i++) {
+ arg = args[i];
+ if (name = (arg && arg.name)) {
+ isNamedFound = false;
+ for(j = 0; j < params.length; j++) {
+ if (!evaldArguments[j] && name === params[j].name) {
+ evaldArguments[j] = arg.value.eval(env);
+ frame.rules.unshift(new(tree.Rule)(name, arg.value.eval(env)));
+ isNamedFound = true;
+ break;
+ }
+ }
+ if (isNamedFound) {
+ args.splice(i, 1);
+ i--;
+ continue;
+ } else {
+ throw { type: 'Runtime', message: "Named argument for " + this.name +
+ ' ' + args[i].name + ' not found' };
+ }
+ }
+ }
+ }
+ argIndex = 0;
+ for (i = 0; i < params.length; i++) {
+ if (evaldArguments[i]) { continue; }
+
+ arg = args && args[argIndex];
+
+ if (name = params[i].name) {
+ if (params[i].variadic && args) {
+ varargs = [];
+ for (j = argIndex; j < args.length; j++) {
+ varargs.push(args[j].value.eval(env));
+ }
+ frame.rules.unshift(new(tree.Rule)(name, new(tree.Expression)(varargs).eval(env)));
+ } else {
+ val = arg && arg.value;
+ if (val) {
+ val = val.eval(env);
+ } else if (params[i].value) {
+ val = params[i].value.eval(mixinEnv);
+ frame.resetCache();
+ } else {
+ throw { type: 'Runtime', message: "wrong number of arguments for " + this.name +
+ ' (' + args.length + ' for ' + this.arity + ')' };
+ }
+
+ frame.rules.unshift(new(tree.Rule)(name, val));
+ evaldArguments[i] = val;
+ }
+ }
+
+ if (params[i].variadic && args) {
+ for (j = argIndex; j < args.length; j++) {
+ evaldArguments[j] = args[j].value.eval(env);
+ }
+ }
+ argIndex++;
+ }
+
+ return frame;
+ },
+ eval: function (env, args, important) {
+ var _arguments = [],
+ mixinFrames = this.frames.concat(env.frames),
+ frame = this.evalParams(env, new(tree.evalEnv)(env, mixinFrames), args, _arguments),
+ rules, ruleset;
+
+ frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env)));
+
+ rules = this.rules.slice(0);
+
+ ruleset = new(tree.Ruleset)(null, rules);
+ ruleset.originalRuleset = this;
+ ruleset = ruleset.eval(new(tree.evalEnv)(env, [this, frame].concat(mixinFrames)));
+ if (important) {
+ ruleset = this.parent.makeImportant.apply(ruleset);
+ }
+ return ruleset;
+ },
+ matchCondition: function (args, env) {
+ if (this.condition && !this.condition.eval(
+ new(tree.evalEnv)(env,
+ [this.evalParams(env, new(tree.evalEnv)(env, this.frames.concat(env.frames)), args, [])] // the parameter variables
+ .concat(this.frames) // the parent namespace/mixin frames
+ .concat(env.frames)))) { // the current environment frames
+ return false;
+ }
+ return true;
+ },
+ matchArgs: function (args, env) {
+ var argsLength = (args && args.length) || 0, len;
+
+ if (! this.variadic) {
+ if (argsLength < this.required) { return false; }
+ if (argsLength > this.params.length) { return false; }
+ } else {
+ if (argsLength < (this.required - 1)) { return false; }
+ }
+
+ len = Math.min(argsLength, this.arity);
+
+ for (var i = 0; i < len; i++) {
+ if (!this.params[i].name && !this.params[i].variadic) {
+ if (args[i].value.eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Negative = function (node) {
+ this.value = node;
+};
+tree.Negative.prototype = {
+ type: "Negative",
+ accept: function (visitor) {
+ this.value = visitor.visit(this.value);
+ },
+ genCSS: function (env, output) {
+ output.add('-');
+ this.value.genCSS(env, output);
+ },
+ toCSS: tree.toCSS,
+ eval: function (env) {
+ if (env.isMathOn()) {
+ return (new(tree.Operation)('*', [new(tree.Dimension)(-1), this.value])).eval(env);
+ }
+ return new(tree.Negative)(this.value.eval(env));
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Operation = function (op, operands, isSpaced) {
+ this.op = op.trim();
+ this.operands = operands;
+ this.isSpaced = isSpaced;
+};
+tree.Operation.prototype = {
+ type: "Operation",
+ accept: function (visitor) {
+ this.operands = visitor.visit(this.operands);
+ },
+ eval: function (env) {
+ var a = this.operands[0].eval(env),
+ b = this.operands[1].eval(env),
+ temp;
+
+ if (env.isMathOn()) {
+ if (a instanceof tree.Dimension && b instanceof tree.Color) {
+ if (this.op === '*' || this.op === '+') {
+ temp = b, b = a, a = temp;
+ } else {
+ throw { type: "Operation",
+ message: "Can't substract or divide a color from a number" };
+ }
+ }
+ if (!a.operate) {
+ throw { type: "Operation",
+ message: "Operation on an invalid type" };
+ }
+
+ return a.operate(env, this.op, b);
+ } else {
+ return new(tree.Operation)(this.op, [a, b], this.isSpaced);
+ }
+ },
+ genCSS: function (env, output) {
+ this.operands[0].genCSS(env, output);
+ if (this.isSpaced) {
+ output.add(" ");
+ }
+ output.add(this.op);
+ if (this.isSpaced) {
+ output.add(" ");
+ }
+ this.operands[1].genCSS(env, output);
+ },
+ toCSS: tree.toCSS
+};
+
+tree.operate = function (env, op, a, b) {
+ switch (op) {
+ case '+': return a + b;
+ case '-': return a - b;
+ case '*': return a * b;
+ case '/': return a / b;
+ }
+};
+
+})(require('../tree'));
+
+
+(function (tree) {
+
+tree.Paren = function (node) {
+ this.value = node;
+};
+tree.Paren.prototype = {
+ type: "Paren",
+ accept: function (visitor) {
+ this.value = visitor.visit(this.value);
+ },
+ genCSS: function (env, output) {
+ output.add('(');
+ this.value.genCSS(env, output);
+ output.add(')');
+ },
+ toCSS: tree.toCSS,
+ eval: function (env) {
+ return new(tree.Paren)(this.value.eval(env));
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Quoted = function (str, content, escaped, index, currentFileInfo) {
+ this.escaped = escaped;
+ this.value = content || '';
+ this.quote = str.charAt(0);
+ this.index = index;
+ this.currentFileInfo = currentFileInfo;
+};
+tree.Quoted.prototype = {
+ type: "Quoted",
+ genCSS: function (env, output) {
+ if (!this.escaped) {
+ output.add(this.quote, this.currentFileInfo, this.index);
+ }
+ output.add(this.value);
+ if (!this.escaped) {
+ output.add(this.quote);
+ }
+ },
+ toCSS: tree.toCSS,
+ eval: function (env) {
+ var that = this;
+ var value = this.value.replace(/`([^`]+)`/g, function (_, exp) {
+ return new(tree.JavaScript)(exp, that.index, true).eval(env).value;
+ }).replace(/@\{([\w-]+)\}/g, function (_, name) {
+ var v = new(tree.Variable)('@' + name, that.index, that.currentFileInfo).eval(env, true);
+ return (v instanceof tree.Quoted) ? v.value : v.toCSS();
+ });
+ return new(tree.Quoted)(this.quote + value + this.quote, value, this.escaped, this.index, this.currentFileInfo);
+ },
+ compare: function (x) {
+ if (!x.toCSS) {
+ return -1;
+ }
+
+ var left = this.toCSS(),
+ right = x.toCSS();
+
+ if (left === right) {
+ return 0;
+ }
+
+ return left < right ? -1 : 1;
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Rule = function (name, value, important, merge, index, currentFileInfo, inline) {
+ this.name = name;
+ this.value = (value instanceof tree.Value) ? value : new(tree.Value)([value]);
+ this.important = important ? ' ' + important.trim() : '';
+ this.merge = merge;
+ this.index = index;
+ this.currentFileInfo = currentFileInfo;
+ this.inline = inline || false;
+ this.variable = (name.charAt(0) === '@');
+};
+
+tree.Rule.prototype = {
+ type: "Rule",
+ accept: function (visitor) {
+ this.value = visitor.visit(this.value);
+ },
+ genCSS: function (env, output) {
+ output.add(this.name + (env.compress ? ':' : ': '), this.currentFileInfo, this.index);
+ try {
+ this.value.genCSS(env, output);
+ }
+ catch(e) {
+ e.index = this.index;
+ e.filename = this.currentFileInfo.filename;
+ throw e;
+ }
+ output.add(this.important + ((this.inline || (env.lastRule && env.compress)) ? "" : ";"), this.currentFileInfo, this.index);
+ },
+ toCSS: tree.toCSS,
+ eval: function (env) {
+ var strictMathBypass = false;
+ if (this.name === "font" && !env.strictMath) {
+ strictMathBypass = true;
+ env.strictMath = true;
+ }
+ try {
+ return new(tree.Rule)(this.name,
+ this.value.eval(env),
+ this.important,
+ this.merge,
+ this.index, this.currentFileInfo, this.inline);
+ }
+ finally {
+ if (strictMathBypass) {
+ env.strictMath = false;
+ }
+ }
+ },
+ makeImportant: function () {
+ return new(tree.Rule)(this.name,
+ this.value,
+ "!important",
+ this.merge,
+ this.index, this.currentFileInfo, this.inline);
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Ruleset = function (selectors, rules, strictImports) {
+ this.selectors = selectors;
+ this.rules = rules;
+ this._lookups = {};
+ this.strictImports = strictImports;
+};
+tree.Ruleset.prototype = {
+ type: "Ruleset",
+ accept: function (visitor) {
+ if (this.paths) {
+ for(var i = 0; i < this.paths.length; i++) {
+ this.paths[i] = visitor.visit(this.paths[i]);
+ }
+ } else {
+ this.selectors = visitor.visit(this.selectors);
+ }
+ this.rules = visitor.visit(this.rules);
+ },
+ eval: function (env) {
+ var selectors = this.selectors && this.selectors.map(function (s) { return s.eval(env); });
+ var ruleset = new(tree.Ruleset)(selectors, this.rules.slice(0), this.strictImports);
+ var rules;
+ var rule;
+ var i;
+
+ ruleset.originalRuleset = this;
+ ruleset.root = this.root;
+ ruleset.firstRoot = this.firstRoot;
+ ruleset.allowImports = this.allowImports;
+
+ if(this.debugInfo) {
+ ruleset.debugInfo = this.debugInfo;
+ }
+
+ // push the current ruleset to the frames stack
+ env.frames.unshift(ruleset);
+
+ // currrent selectors
+ if (!env.selectors) {
+ env.selectors = [];
+ }
+ env.selectors.unshift(this.selectors);
+
+ // Evaluate imports
+ if (ruleset.root || ruleset.allowImports || !ruleset.strictImports) {
+ ruleset.evalImports(env);
+ }
+
+ // Store the frames around mixin definitions,
+ // so they can be evaluated like closures when the time comes.
+ for (i = 0; i < ruleset.rules.length; i++) {
+ if (ruleset.rules[i] instanceof tree.mixin.Definition) {
+ ruleset.rules[i].frames = env.frames.slice(0);
+ }
+ }
+
+ var mediaBlockCount = (env.mediaBlocks && env.mediaBlocks.length) || 0;
+
+ // Evaluate mixin calls.
+ for (i = 0; i < ruleset.rules.length; i++) {
+ if (ruleset.rules[i] instanceof tree.mixin.Call) {
+ /*jshint loopfunc:true */
+ rules = ruleset.rules[i].eval(env).filter(function(r) {
+ if ((r instanceof tree.Rule) && r.variable) {
+ // do not pollute the scope if the variable is
+ // already there. consider returning false here
+ // but we need a way to "return" variable from mixins
+ return !(ruleset.variable(r.name));
+ }
+ return true;
+ });
+ ruleset.rules.splice.apply(ruleset.rules, [i, 1].concat(rules));
+ i += rules.length-1;
+ ruleset.resetCache();
+ }
+ }
+
+ // Evaluate everything else
+ for (i = 0; i < ruleset.rules.length; i++) {
+ rule = ruleset.rules[i];
+
+ if (! (rule instanceof tree.mixin.Definition)) {
+ ruleset.rules[i] = rule.eval ? rule.eval(env) : rule;
+ }
+ }
+
+ // Pop the stack
+ env.frames.shift();
+ env.selectors.shift();
+
+ if (env.mediaBlocks) {
+ for (i = mediaBlockCount; i < env.mediaBlocks.length; i++) {
+ env.mediaBlocks[i].bubbleSelectors(selectors);
+ }
+ }
+
+ return ruleset;
+ },
+ evalImports: function(env) {
+ var i, rules;
+ for (i = 0; i < this.rules.length; i++) {
+ if (this.rules[i] instanceof tree.Import) {
+ rules = this.rules[i].eval(env);
+ if (typeof rules.length === "number") {
+ this.rules.splice.apply(this.rules, [i, 1].concat(rules));
+ i+= rules.length-1;
+ } else {
+ this.rules.splice(i, 1, rules);
+ }
+ this.resetCache();
+ }
+ }
+ },
+ makeImportant: function() {
+ return new tree.Ruleset(this.selectors, this.rules.map(function (r) {
+ if (r.makeImportant) {
+ return r.makeImportant();
+ } else {
+ return r;
+ }
+ }), this.strictImports);
+ },
+ matchArgs: function (args) {
+ return !args || args.length === 0;
+ },
+ matchCondition: function (args, env) {
+ var lastSelector = this.selectors[this.selectors.length-1];
+ if (lastSelector.condition &&
+ !lastSelector.condition.eval(
+ new(tree.evalEnv)(env,
+ env.frames))) {
+ return false;
+ }
+ return true;
+ },
+ resetCache: function () {
+ this._rulesets = null;
+ this._variables = null;
+ this._lookups = {};
+ },
+ variables: function () {
+ if (this._variables) { return this._variables; }
+ else {
+ return this._variables = this.rules.reduce(function (hash, r) {
+ if (r instanceof tree.Rule && r.variable === true) {
+ hash[r.name] = r;
+ }
+ return hash;
+ }, {});
+ }
+ },
+ variable: function (name) {
+ return this.variables()[name];
+ },
+ rulesets: function () {
+ return this.rules.filter(function (r) {
+ return (r instanceof tree.Ruleset) || (r instanceof tree.mixin.Definition);
+ });
+ },
+ find: function (selector, self) {
+ self = self || this;
+ var rules = [], match,
+ key = selector.toCSS();
+
+ if (key in this._lookups) { return this._lookups[key]; }
+
+ this.rulesets().forEach(function (rule) {
+ if (rule !== self) {
+ for (var j = 0; j < rule.selectors.length; j++) {
+ if (match = selector.match(rule.selectors[j])) {
+ if (selector.elements.length > match) {
+ Array.prototype.push.apply(rules, rule.find(
+ new(tree.Selector)(selector.elements.slice(match)), self));
+ } else {
+ rules.push(rule);
+ }
+ break;
+ }
+ }
+ }
+ });
+ return this._lookups[key] = rules;
+ },
+ genCSS: function (env, output) {
+ var i, j,
+ ruleNodes = [],
+ rulesetNodes = [],
+ debugInfo, // Line number debugging
+ rule,
+ firstRuleset = true,
+ path;
+
+ env.tabLevel = (env.tabLevel || 0);
+
+ if (!this.root) {
+ env.tabLevel++;
+ }
+
+ var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join(" "),
+ tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" ");
+
+ for (i = 0; i < this.rules.length; i++) {
+ rule = this.rules[i];
+ if (rule.rules || (rule instanceof tree.Media) || rule instanceof tree.Directive || (this.root && rule instanceof tree.Comment)) {
+ rulesetNodes.push(rule);
+ } else {
+ ruleNodes.push(rule);
+ }
+ }
+
+ // If this is the root node, we don't render
+ // a selector, or {}.
+ if (!this.root) {
+ debugInfo = tree.debugInfo(env, this, tabSetStr);
+
+ if (debugInfo) {
+ output.add(debugInfo);
+ output.add(tabSetStr);
+ }
+
+ for(i = 0; i < this.paths.length; i++) {
+ path = this.paths[i];
+ env.firstSelector = true;
+ for(j = 0; j < path.length; j++) {
+ path[j].genCSS(env, output);
+ env.firstSelector = false;
+ }
+ if (i + 1 < this.paths.length) {
+ output.add(env.compress ? ',' : (',\n' + tabSetStr));
+ }
+ }
+
+ output.add((env.compress ? '{' : ' {\n') + tabRuleStr);
+ }
+
+ // Compile rules and rulesets
+ for (i = 0; i < ruleNodes.length; i++) {
+ rule = ruleNodes[i];
+
+ // @page{ directive ends up with root elements inside it, a mix of rules and rulesets
+ // In this instance we do not know whether it is the last property
+ if (i + 1 === ruleNodes.length && (!this.root || rulesetNodes.length === 0 || this.firstRoot)) {
+ env.lastRule = true;
+ }
+
+ if (rule.genCSS) {
+ rule.genCSS(env, output);
+ } else if (rule.value) {
+ output.add(rule.value.toString());
+ }
+
+ if (!env.lastRule) {
+ output.add(env.compress ? '' : ('\n' + tabRuleStr));
+ } else {
+ env.lastRule = false;
+ }
+ }
+
+ if (!this.root) {
+ output.add((env.compress ? '}' : '\n' + tabSetStr + '}'));
+ env.tabLevel--;
+ }
+
+ for (i = 0; i < rulesetNodes.length; i++) {
+ if (ruleNodes.length && firstRuleset) {
+ output.add((env.compress ? "" : "\n") + (this.root ? tabRuleStr : tabSetStr));
+ }
+ if (!firstRuleset) {
+ output.add((env.compress ? "" : "\n") + (this.root ? tabRuleStr : tabSetStr));
+ }
+ firstRuleset = false;
+ rulesetNodes[i].genCSS(env, output);
+ }
+
+ if (!output.isEmpty() && !env.compress && this.firstRoot) {
+ output.add('\n');
+ }
+ },
+
+ toCSS: tree.toCSS,
+
+ markReferenced: function () {
+ for (var s = 0; s < this.selectors.length; s++) {
+ this.selectors[s].markReferenced();
+ }
+ },
+
+ joinSelectors: function (paths, context, selectors) {
+ for (var s = 0; s < selectors.length; s++) {
+ this.joinSelector(paths, context, selectors[s]);
+ }
+ },
+
+ joinSelector: function (paths, context, selector) {
+
+ var i, j, k,
+ hasParentSelector, newSelectors, el, sel, parentSel,
+ newSelectorPath, afterParentJoin, newJoinedSelector,
+ newJoinedSelectorEmpty, lastSelector, currentElements,
+ selectorsMultiplied;
+
+ for (i = 0; i < selector.elements.length; i++) {
+ el = selector.elements[i];
+ if (el.value === '&') {
+ hasParentSelector = true;
+ }
+ }
+
+ if (!hasParentSelector) {
+ if (context.length > 0) {
+ for (i = 0; i < context.length; i++) {
+ paths.push(context[i].concat(selector));
+ }
+ }
+ else {
+ paths.push([selector]);
+ }
+ return;
+ }
+
+ // The paths are [[Selector]]
+ // The first list is a list of comma seperated selectors
+ // The inner list is a list of inheritance seperated selectors
+ // e.g.
+ // .a, .b {
+ // .c {
+ // }
+ // }
+ // == [[.a] [.c]] [[.b] [.c]]
+ //
+
+ // the elements from the current selector so far
+ currentElements = [];
+ // the current list of new selectors to add to the path.
+ // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
+ // by the parents
+ newSelectors = [[]];
+
+ for (i = 0; i < selector.elements.length; i++) {
+ el = selector.elements[i];
+ // non parent reference elements just get added
+ if (el.value !== "&") {
+ currentElements.push(el);
+ } else {
+ // the new list of selectors to add
+ selectorsMultiplied = [];
+
+ // merge the current list of non parent selector elements
+ // on to the current list of selectors to add
+ if (currentElements.length > 0) {
+ this.mergeElementsOnToSelectors(currentElements, newSelectors);
+ }
+
+ // loop through our current selectors
+ for (j = 0; j < newSelectors.length; j++) {
+ sel = newSelectors[j];
+ // if we don't have any parent paths, the & might be in a mixin so that it can be used
+ // whether there are parents or not
+ if (context.length === 0) {
+ // the combinator used on el should now be applied to the next element instead so that
+ // it is not lost
+ if (sel.length > 0) {
+ sel[0].elements = sel[0].elements.slice(0);
+ sel[0].elements.push(new(tree.Element)(el.combinator, '', 0, el.index, el.currentFileInfo));
+ }
+ selectorsMultiplied.push(sel);
+ }
+ else {
+ // and the parent selectors
+ for (k = 0; k < context.length; k++) {
+ parentSel = context[k];
+ // We need to put the current selectors
+ // then join the last selector's elements on to the parents selectors
+
+ // our new selector path
+ newSelectorPath = [];
+ // selectors from the parent after the join
+ afterParentJoin = [];
+ newJoinedSelectorEmpty = true;
+
+ //construct the joined selector - if & is the first thing this will be empty,
+ // if not newJoinedSelector will be the last set of elements in the selector
+ if (sel.length > 0) {
+ newSelectorPath = sel.slice(0);
+ lastSelector = newSelectorPath.pop();
+ newJoinedSelector = selector.createDerived(lastSelector.elements.slice(0));
+ newJoinedSelectorEmpty = false;
+ }
+ else {
+ newJoinedSelector = selector.createDerived([]);
+ }
+
+ //put together the parent selectors after the join
+ if (parentSel.length > 1) {
+ afterParentJoin = afterParentJoin.concat(parentSel.slice(1));
+ }
+
+ if (parentSel.length > 0) {
+ newJoinedSelectorEmpty = false;
+
+ // join the elements so far with the first part of the parent
+ newJoinedSelector.elements.push(new(tree.Element)(el.combinator, parentSel[0].elements[0].value, el.index, el.currentFileInfo));
+ newJoinedSelector.elements = newJoinedSelector.elements.concat(parentSel[0].elements.slice(1));
+ }
+
+ if (!newJoinedSelectorEmpty) {
+ // now add the joined selector
+ newSelectorPath.push(newJoinedSelector);
+ }
+
+ // and the rest of the parent
+ newSelectorPath = newSelectorPath.concat(afterParentJoin);
+
+ // add that to our new set of selectors
+ selectorsMultiplied.push(newSelectorPath);
+ }
+ }
+ }
+
+ // our new selectors has been multiplied, so reset the state
+ newSelectors = selectorsMultiplied;
+ currentElements = [];
+ }
+ }
+
+ // if we have any elements left over (e.g. .a& .b == .b)
+ // add them on to all the current selectors
+ if (currentElements.length > 0) {
+ this.mergeElementsOnToSelectors(currentElements, newSelectors);
+ }
+
+ for (i = 0; i < newSelectors.length; i++) {
+ if (newSelectors[i].length > 0) {
+ paths.push(newSelectors[i]);
+ }
+ }
+ },
+
+ mergeElementsOnToSelectors: function(elements, selectors) {
+ var i, sel;
+
+ if (selectors.length === 0) {
+ selectors.push([ new(tree.Selector)(elements) ]);
+ return;
+ }
+
+ for (i = 0; i < selectors.length; i++) {
+ sel = selectors[i];
+
+ // if the previous thing in sel is a parent this needs to join on to it
+ if (sel.length > 0) {
+ sel[sel.length - 1] = sel[sel.length - 1].createDerived(sel[sel.length - 1].elements.concat(elements));
+ }
+ else {
+ sel.push(new(tree.Selector)(elements));
+ }
+ }
+ }
+};
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Selector = function (elements, extendList, condition, index, currentFileInfo, isReferenced) {
+ this.elements = elements;
+ this.extendList = extendList || [];
+ this.condition = condition;
+ this.currentFileInfo = currentFileInfo || {};
+ this.isReferenced = isReferenced;
+ if (!condition) {
+ this.evaldCondition = true;
+ }
+};
+tree.Selector.prototype = {
+ type: "Selector",
+ accept: function (visitor) {
+ this.elements = visitor.visit(this.elements);
+ this.extendList = visitor.visit(this.extendList);
+ this.condition = visitor.visit(this.condition);
+ },
+ createDerived: function(elements, extendList, evaldCondition) {
+ /*jshint eqnull:true */
+ evaldCondition = evaldCondition != null ? evaldCondition : this.evaldCondition;
+ var newSelector = new(tree.Selector)(elements, extendList || this.extendList, this.condition, this.index, this.currentFileInfo, this.isReferenced);
+ newSelector.evaldCondition = evaldCondition;
+ return newSelector;
+ },
+ match: function (other) {
+ var elements = this.elements,
+ len = elements.length,
+ oelements, olen, max, i;
+
+ oelements = other.elements.slice(
+ (other.elements.length && other.elements[0].value === "&") ? 1 : 0);
+ olen = oelements.length;
+ max = Math.min(len, olen);
+
+ if (olen === 0 || len < olen) {
+ return 0;
+ } else {
+ for (i = 0; i < max; i++) {
+ if (elements[i].value !== oelements[i].value) {
+ return 0;
+ }
+ }
+ }
+ return max; // return number of matched selectors
+ },
+ eval: function (env) {
+ var evaldCondition = this.condition && this.condition.eval(env);
+
+ return this.createDerived(this.elements.map(function (e) {
+ return e.eval(env);
+ }), this.extendList.map(function(extend) {
+ return extend.eval(env);
+ }), evaldCondition);
+ },
+ genCSS: function (env, output) {
+ var i, element;
+ if ((!env || !env.firstSelector) && this.elements[0].combinator.value === "") {
+ output.add(' ', this.currentFileInfo, this.index);
+ }
+ if (!this._css) {
+ //TODO caching? speed comparison?
+ for(i = 0; i < this.elements.length; i++) {
+ element = this.elements[i];
+ element.genCSS(env, output);
+ }
+ }
+ },
+ toCSS: tree.toCSS,
+ markReferenced: function () {
+ this.isReferenced = true;
+ },
+ getIsReferenced: function() {
+ return !this.currentFileInfo.reference || this.isReferenced;
+ },
+ getIsOutput: function() {
+ return this.evaldCondition;
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.UnicodeDescriptor = function (value) {
+ this.value = value;
+};
+tree.UnicodeDescriptor.prototype = {
+ type: "UnicodeDescriptor",
+ genCSS: function (env, output) {
+ output.add(this.value);
+ },
+ toCSS: tree.toCSS,
+ eval: function () { return this; }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.URL = function (val, currentFileInfo) {
+ this.value = val;
+ this.currentFileInfo = currentFileInfo;
+};
+tree.URL.prototype = {
+ type: "Url",
+ accept: function (visitor) {
+ this.value = visitor.visit(this.value);
+ },
+ genCSS: function (env, output) {
+ output.add("url(");
+ this.value.genCSS(env, output);
+ output.add(")");
+ },
+ toCSS: tree.toCSS,
+ eval: function (ctx) {
+ var val = this.value.eval(ctx), rootpath;
+
+ // Add the base path if the URL is relative
+ rootpath = this.currentFileInfo && this.currentFileInfo.rootpath;
+ if (rootpath && typeof val.value === "string" && ctx.isPathRelative(val.value)) {
+ if (!val.quote) {
+ rootpath = rootpath.replace(/[\(\)'"\s]/g, function(match) { return "\\"+match; });
+ }
+ val.value = rootpath + val.value;
+ }
+
+ val.value = ctx.normalizePath(val.value);
+
+ return new(tree.URL)(val, null);
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Value = function (value) {
+ this.value = value;
+};
+tree.Value.prototype = {
+ type: "Value",
+ accept: function (visitor) {
+ this.value = visitor.visit(this.value);
+ },
+ eval: function (env) {
+ if (this.value.length === 1) {
+ return this.value[0].eval(env);
+ } else {
+ return new(tree.Value)(this.value.map(function (v) {
+ return v.eval(env);
+ }));
+ }
+ },
+ genCSS: function (env, output) {
+ var i;
+ for(i = 0; i < this.value.length; i++) {
+ this.value[i].genCSS(env, output);
+ if (i+1 < this.value.length) {
+ output.add((env && env.compress) ? ',' : ', ');
+ }
+ }
+ },
+ toCSS: tree.toCSS
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+tree.Variable = function (name, index, currentFileInfo) {
+ this.name = name;
+ this.index = index;
+ this.currentFileInfo = currentFileInfo;
+};
+tree.Variable.prototype = {
+ type: "Variable",
+ eval: function (env) {
+ var variable, v, name = this.name;
+
+ if (name.indexOf('@@') === 0) {
+ name = '@' + new(tree.Variable)(name.slice(1)).eval(env).value;
+ }
+
+ if (this.evaluating) {
+ throw { type: 'Name',
+ message: "Recursive variable definition for " + name,
+ filename: this.currentFileInfo.file,
+ index: this.index };
+ }
+
+ this.evaluating = true;
+
+ if (variable = tree.find(env.frames, function (frame) {
+ if (v = frame.variable(name)) {
+ return v.value.eval(env);
+ }
+ })) {
+ this.evaluating = false;
+ return variable;
+ }
+ else {
+ throw { type: 'Name',
+ message: "variable " + name + " is undefined",
+ filename: this.currentFileInfo.filename,
+ index: this.index };
+ }
+ }
+};
+
+})(require('../tree'));
+
+(function (tree) {
+
+ var parseCopyProperties = [
+ 'paths', // option - unmodified - paths to search for imports on
+ 'optimization', // option - optimization level (for the chunker)
+ 'files', // list of files that have been imported, used for import-once
+ 'contents', // browser-only, contents of all the files
+ 'relativeUrls', // option - whether to adjust URL's to be relative
+ 'rootpath', // option - rootpath to append to URL's
+ 'strictImports', // option -
+ 'insecure', // option - whether to allow imports from insecure ssl hosts
+ 'dumpLineNumbers', // option - whether to dump line numbers
+ 'compress', // option - whether to compress
+ 'processImports', // option - whether to process imports. if false then imports will not be imported
+ 'syncImport', // option - whether to import synchronously
+ 'javascriptEnabled',// option - whether JavaScript is enabled. if undefined, defaults to true
+ 'mime', // browser only - mime type for sheet import
+ 'useFileCache', // browser only - whether to use the per file session cache
+ 'currentFileInfo' // information about the current file - for error reporting and importing and making urls relative etc.
+ ];
+
+ //currentFileInfo = {
+ // 'relativeUrls' - option - whether to adjust URL's to be relative
+ // 'filename' - full resolved filename of current file
+ // 'rootpath' - path to append to normal URLs for this node
+ // 'currentDirectory' - path to the current file, absolute
+ // 'rootFilename' - filename of the base file
+ // 'entryPath' - absolute path to the entry file
+ // 'reference' - whether the file should not be output and only output parts that are referenced
+
+ tree.parseEnv = function(options) {
+ copyFromOriginal(options, this, parseCopyProperties);
+
+ if (!this.contents) { this.contents = {}; }
+ if (!this.files) { this.files = {}; }
+
+ if (!this.currentFileInfo) {
+ var filename = (options && options.filename) || "input";
+ var entryPath = filename.replace(/[^\/\\]*$/, "");
+ if (options) {
+ options.filename = null;
+ }
+ this.currentFileInfo = {
+ filename: filename,
+ relativeUrls: this.relativeUrls,
+ rootpath: (options && options.rootpath) || "",
+ currentDirectory: entryPath,
+ entryPath: entryPath,
+ rootFilename: filename
+ };
+ }
+ };
+
+ var evalCopyProperties = [
+ 'silent', // whether to swallow errors and warnings
+ 'verbose', // whether to log more activity
+ 'compress', // whether to compress
+ 'yuicompress', // whether to compress with the outside tool yui compressor
+ 'ieCompat', // whether to enforce IE compatibility (IE8 data-uri)
+ 'strictMath', // whether math has to be within parenthesis
+ 'strictUnits', // whether units need to evaluate correctly
+ 'cleancss', // whether to compress with clean-css
+ 'sourceMap', // whether to output a source map
+ 'importMultiple'// whether we are currently importing multiple copies
+ ];
+
+ tree.evalEnv = function(options, frames) {
+ copyFromOriginal(options, this, evalCopyProperties);
+
+ this.frames = frames || [];
+ };
+
+ tree.evalEnv.prototype.inParenthesis = function () {
+ if (!this.parensStack) {
+ this.parensStack = [];
+ }
+ this.parensStack.push(true);
+ };
+
+ tree.evalEnv.prototype.outOfParenthesis = function () {
+ this.parensStack.pop();
+ };
+
+ tree.evalEnv.prototype.isMathOn = function () {
+ return this.strictMath ? (this.parensStack && this.parensStack.length) : true;
+ };
+
+ tree.evalEnv.prototype.isPathRelative = function (path) {
+ return !/^(?:[a-z-]+:|\/)/.test(path);
+ };
+
+ tree.evalEnv.prototype.normalizePath = function( path ) {
+ var
+ segments = path.split("/").reverse(),
+ segment;
+
+ path = [];
+ while (segments.length !== 0 ) {
+ segment = segments.pop();
+ switch( segment ) {
+ case ".":
+ break;
+ case "..":
+ if ((path.length === 0) || (path[path.length - 1] === "..")) {
+ path.push( segment );
+ } else {
+ path.pop();
+ }
+ break;
+ default:
+ path.push( segment );
+ break;
+ }
+ }
+
+ return path.join("/");
+ };
+
+ //todo - do the same for the toCSS env
+ //tree.toCSSEnv = function (options) {
+ //};
+
+ var copyFromOriginal = function(original, destination, propertiesToCopy) {
+ if (!original) { return; }
+
+ for(var i = 0; i < propertiesToCopy.length; i++) {
+ if (original.hasOwnProperty(propertiesToCopy[i])) {
+ destination[propertiesToCopy[i]] = original[propertiesToCopy[i]];
+ }
+ }
+ };
+
+})(require('./tree'));
+
+(function (tree) {
+
+ tree.visitor = function(implementation) {
+ this._implementation = implementation;
+ };
+
+ tree.visitor.prototype = {
+ visit: function(node) {
+
+ if (node instanceof Array) {
+ return this.visitArray(node);
+ }
+
+ if (!node || !node.type) {
+ return node;
+ }
+
+ var funcName = "visit" + node.type,
+ func = this._implementation[funcName],
+ visitArgs, newNode;
+ if (func) {
+ visitArgs = {visitDeeper: true};
+ newNode = func.call(this._implementation, node, visitArgs);
+ if (this._implementation.isReplacing) {
+ node = newNode;
+ }
+ }
+ if ((!visitArgs || visitArgs.visitDeeper) && node && node.accept) {
+ node.accept(this);
+ }
+ funcName = funcName + "Out";
+ if (this._implementation[funcName]) {
+ this._implementation[funcName](node);
+ }
+ return node;
+ },
+ visitArray: function(nodes) {
+ var i, newNodes = [];
+ for(i = 0; i < nodes.length; i++) {
+ var evald = this.visit(nodes[i]);
+ if (evald instanceof Array) {
+ evald = this.flatten(evald);
+ newNodes = newNodes.concat(evald);
+ } else {
+ newNodes.push(evald);
+ }
+ }
+ if (this._implementation.isReplacing) {
+ return newNodes;
+ }
+ return nodes;
+ },
+ doAccept: function (node) {
+ node.accept(this);
+ },
+ flatten: function(arr, master) {
+ return arr.reduce(this.flattenReduce.bind(this), master || []);
+ },
+ flattenReduce: function(sum, element) {
+ if (element instanceof Array) {
+ sum = this.flatten(element, sum);
+ } else {
+ sum.push(element);
+ }
+ return sum;
+ }
+ };
+
+})(require('./tree'));
+(function (tree) {
+ tree.importVisitor = function(importer, finish, evalEnv) {
+ this._visitor = new tree.visitor(this);
+ this._importer = importer;
+ this._finish = finish;
+ this.env = evalEnv || new tree.evalEnv();
+ this.importCount = 0;
+ };
+
+ tree.importVisitor.prototype = {
+ isReplacing: true,
+ run: function (root) {
+ var error;
+ try {
+ // process the contents
+ this._visitor.visit(root);
+ }
+ catch(e) {
+ error = e;
+ }
+
+ this.isFinished = true;
+
+ if (this.importCount === 0) {
+ this._finish(error);
+ }
+ },
+ visitImport: function (importNode, visitArgs) {
+ var importVisitor = this,
+ evaldImportNode,
+ inlineCSS = importNode.options.inline;
+
+ if (!importNode.css || inlineCSS) {
+
+ try {
+ evaldImportNode = importNode.evalForImport(this.env);
+ } catch(e){
+ if (!e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
+ // attempt to eval properly and treat as css
+ importNode.css = true;
+ // if that fails, this error will be thrown
+ importNode.error = e;
+ }
+
+ if (evaldImportNode && (!evaldImportNode.css || inlineCSS)) {
+ importNode = evaldImportNode;
+ this.importCount++;
+ var env = new tree.evalEnv(this.env, this.env.frames.slice(0));
+
+ if (importNode.options.multiple) {
+ env.importMultiple = true;
+ }
+
+ this._importer.push(importNode.getPath(), importNode.currentFileInfo, importNode.options, function (e, root, imported, fullPath) {
+ if (e && !e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
+
+ if (imported && !env.importMultiple) { importNode.skip = imported; }
+
+ var subFinish = function(e) {
+ importVisitor.importCount--;
+
+ if (importVisitor.importCount === 0 && importVisitor.isFinished) {
+ importVisitor._finish(e);
+ }
+ };
+
+ if (root) {
+ importNode.root = root;
+ importNode.importedFilename = fullPath;
+ if (!inlineCSS && !importNode.skip) {
+ new(tree.importVisitor)(importVisitor._importer, subFinish, env)
+ .run(root);
+ return;
+ }
+ }
+
+ subFinish();
+ });
+ }
+ }
+ visitArgs.visitDeeper = false;
+ return importNode;
+ },
+ visitRule: function (ruleNode, visitArgs) {
+ visitArgs.visitDeeper = false;
+ return ruleNode;
+ },
+ visitDirective: function (directiveNode, visitArgs) {
+ this.env.frames.unshift(directiveNode);
+ return directiveNode;
+ },
+ visitDirectiveOut: function (directiveNode) {
+ this.env.frames.shift();
+ },
+ visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
+ this.env.frames.unshift(mixinDefinitionNode);
+ return mixinDefinitionNode;
+ },
+ visitMixinDefinitionOut: function (mixinDefinitionNode) {
+ this.env.frames.shift();
+ },
+ visitRuleset: function (rulesetNode, visitArgs) {
+ this.env.frames.unshift(rulesetNode);
+ return rulesetNode;
+ },
+ visitRulesetOut: function (rulesetNode) {
+ this.env.frames.shift();
+ },
+ visitMedia: function (mediaNode, visitArgs) {
+ this.env.frames.unshift(mediaNode.ruleset);
+ return mediaNode;
+ },
+ visitMediaOut: function (mediaNode) {
+ this.env.frames.shift();
+ }
+ };
+
+})(require('./tree'));
+(function (tree) {
+ tree.joinSelectorVisitor = function() {
+ this.contexts = [[]];
+ this._visitor = new tree.visitor(this);
+ };
+
+ tree.joinSelectorVisitor.prototype = {
+ run: function (root) {
+ return this._visitor.visit(root);
+ },
+ visitRule: function (ruleNode, visitArgs) {
+ visitArgs.visitDeeper = false;
+ },
+ visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
+ visitArgs.visitDeeper = false;
+ },
+
+ visitRuleset: function (rulesetNode, visitArgs) {
+ var context = this.contexts[this.contexts.length - 1];
+ var paths = [];
+ this.contexts.push(paths);
+
+ if (! rulesetNode.root) {
+ rulesetNode.selectors = rulesetNode.selectors.filter(function(selector) { return selector.getIsOutput(); });
+ if (rulesetNode.selectors.length === 0) {
+ rulesetNode.rules.length = 0;
+ }
+ rulesetNode.joinSelectors(paths, context, rulesetNode.selectors);
+ rulesetNode.paths = paths;
+ }
+ },
+ visitRulesetOut: function (rulesetNode) {
+ this.contexts.length = this.contexts.length - 1;
+ },
+ visitMedia: function (mediaNode, visitArgs) {
+ var context = this.contexts[this.contexts.length - 1];
+ mediaNode.rules[0].root = (context.length === 0 || context[0].multiMedia);
+ }
+ };
+
+})(require('./tree'));
+(function (tree) {
+ tree.toCSSVisitor = function(env) {
+ this._visitor = new tree.visitor(this);
+ this._env = env;
+ };
+
+ tree.toCSSVisitor.prototype = {
+ isReplacing: true,
+ run: function (root) {
+ return this._visitor.visit(root);
+ },
+
+ visitRule: function (ruleNode, visitArgs) {
+ if (ruleNode.variable) {
+ return [];
+ }
+ return ruleNode;
+ },
+
+ visitMixinDefinition: function (mixinNode, visitArgs) {
+ return [];
+ },
+
+ visitExtend: function (extendNode, visitArgs) {
+ return [];
+ },
+
+ visitComment: function (commentNode, visitArgs) {
+ if (commentNode.isSilent(this._env)) {
+ return [];
+ }
+ return commentNode;
+ },
+
+ visitMedia: function(mediaNode, visitArgs) {
+ mediaNode.accept(this._visitor);
+ visitArgs.visitDeeper = false;
+
+ if (!mediaNode.rules.length) {
+ return [];
+ }
+ return mediaNode;
+ },
+
+ visitDirective: function(directiveNode, visitArgs) {
+ if (directiveNode.currentFileInfo.reference && !directiveNode.isReferenced) {
+ return [];
+ }
+ if (directiveNode.name === "@charset") {
+ // Only output the debug info together with subsequent @charset definitions
+ // a comment (or @media statement) before the actual @charset directive would
+ // be considered illegal css as it has to be on the first line
+ if (this.charset) {
+ if (directiveNode.debugInfo) {
+ var comment = new tree.Comment("/* " + directiveNode.toCSS(this._env).replace(/\n/g, "")+" */\n");
+ comment.debugInfo = directiveNode.debugInfo;
+ return this._visitor.visit(comment);
+ }
+ return [];
+ }
+ this.charset = true;
+ }
+ return directiveNode;
+ },
+
+ checkPropertiesInRoot: function(rules) {
+ var ruleNode;
+ for(var i = 0; i < rules.length; i++) {
+ ruleNode = rules[i];
+ if (ruleNode instanceof tree.Rule && !ruleNode.variable) {
+ throw { message: "properties must be inside selector blocks, they cannot be in the root.",
+ index: ruleNode.index, filename: ruleNode.currentFileInfo ? ruleNode.currentFileInfo.filename : null};
+ }
+ }
+ },
+
+ visitRuleset: function (rulesetNode, visitArgs) {
+ var rule, rulesets = [];
+ if (rulesetNode.firstRoot) {
+ this.checkPropertiesInRoot(rulesetNode.rules);
+ }
+ if (! rulesetNode.root) {
+
+ rulesetNode.paths = rulesetNode.paths
+ .filter(function(p) {
+ var i;
+ if (p[0].elements[0].combinator.value === ' ') {
+ p[0].elements[0].combinator = new(tree.Combinator)('');
+ }
+ for(i = 0; i < p.length; i++) {
+ if (p[i].getIsReferenced() && p[i].getIsOutput()) {
+ return true;
+ }
+ return false;
+ }
+ });
+
+ // Compile rules and rulesets
+ for (var i = 0; i < rulesetNode.rules.length; i++) {
+ rule = rulesetNode.rules[i];
+
+ if (rule.rules) {
+ // visit because we are moving them out from being a child
+ rulesets.push(this._visitor.visit(rule));
+ rulesetNode.rules.splice(i, 1);
+ i--;
+ continue;
+ }
+ }
+ // accept the visitor to remove rules and refactor itself
+ // then we can decide now whether we want it or not
+ if (rulesetNode.rules.length > 0) {
+ rulesetNode.accept(this._visitor);
+ }
+ visitArgs.visitDeeper = false;
+
+ this._mergeRules(rulesetNode.rules);
+ this._removeDuplicateRules(rulesetNode.rules);
+
+ // now decide whether we keep the ruleset
+ if (rulesetNode.rules.length > 0 && rulesetNode.paths.length > 0) {
+ rulesets.splice(0, 0, rulesetNode);
+ }
+ } else {
+ rulesetNode.accept(this._visitor);
+ visitArgs.visitDeeper = false;
+ if (rulesetNode.firstRoot || rulesetNode.rules.length > 0) {
+ rulesets.splice(0, 0, rulesetNode);
+ }
+ }
+ if (rulesets.length === 1) {
+ return rulesets[0];
+ }
+ return rulesets;
+ },
+
+ _removeDuplicateRules: function(rules) {
+ // remove duplicates
+ var ruleCache = {},
+ ruleList, rule, i;
+ for(i = rules.length - 1; i >= 0 ; i--) {
+ rule = rules[i];
+ if (rule instanceof tree.Rule) {
+ if (!ruleCache[rule.name]) {
+ ruleCache[rule.name] = rule;
+ } else {
+ ruleList = ruleCache[rule.name];
+ if (ruleList instanceof tree.Rule) {
+ ruleList = ruleCache[rule.name] = [ruleCache[rule.name].toCSS(this._env)];
+ }
+ var ruleCSS = rule.toCSS(this._env);
+ if (ruleList.indexOf(ruleCSS) !== -1) {
+ rules.splice(i, 1);
+ } else {
+ ruleList.push(ruleCSS);
+ }
+ }
+ }
+ }
+ },
+
+ _mergeRules: function (rules) {
+ var groups = {},
+ parts,
+ rule,
+ key;
+
+ for (var i = 0; i < rules.length; i++) {
+ rule = rules[i];
+
+ if ((rule instanceof tree.Rule) && rule.merge) {
+ key = [rule.name,
+ rule.important ? "!" : ""].join(",");
+
+ if (!groups[key]) {
+ parts = groups[key] = [];
+ } else {
+ rules.splice(i--, 1);
+ }
+
+ parts.push(rule);
+ }
+ }
+
+ Object.keys(groups).map(function (k) {
+ parts = groups[k];
+
+ if (parts.length > 1) {
+ rule = parts[0];
+
+ rule.value = new (tree.Value)(parts.map(function (p) {
+ return p.value;
+ }));
+ }
+ });
+ }
+ };
+
+})(require('./tree'));
+(function (tree) {
+ /*jshint loopfunc:true */
+
+ tree.extendFinderVisitor = function() {
+ this._visitor = new tree.visitor(this);
+ this.contexts = [];
+ this.allExtendsStack = [[]];
+ };
+
+ tree.extendFinderVisitor.prototype = {
+ run: function (root) {
+ root = this._visitor.visit(root);
+ root.allExtends = this.allExtendsStack[0];
+ return root;
+ },
+ visitRule: function (ruleNode, visitArgs) {
+ visitArgs.visitDeeper = false;
+ },
+ visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
+ visitArgs.visitDeeper = false;
+ },
+ visitRuleset: function (rulesetNode, visitArgs) {
+
+ if (rulesetNode.root) {
+ return;
+ }
+
+ var i, j, extend, allSelectorsExtendList = [], extendList;
+
+ // get &:extend(.a); rules which apply to all selectors in this ruleset
+ for(i = 0; i < rulesetNode.rules.length; i++) {
+ if (rulesetNode.rules[i] instanceof tree.Extend) {
+ allSelectorsExtendList.push(rulesetNode.rules[i]);
+ rulesetNode.extendOnEveryPath = true;
+ }
+ }
+
+ // now find every selector and apply the extends that apply to all extends
+ // and the ones which apply to an individual extend
+ for(i = 0; i < rulesetNode.paths.length; i++) {
+ var selectorPath = rulesetNode.paths[i],
+ selector = selectorPath[selectorPath.length-1];
+ extendList = selector.extendList.slice(0).concat(allSelectorsExtendList).map(function(allSelectorsExtend) {
+ return allSelectorsExtend.clone();
+ });
+ for(j = 0; j < extendList.length; j++) {
+ this.foundExtends = true;
+ extend = extendList[j];
+ extend.findSelfSelectors(selectorPath);
+ extend.ruleset = rulesetNode;
+ if (j === 0) { extend.firstExtendOnThisSelectorPath = true; }
+ this.allExtendsStack[this.allExtendsStack.length-1].push(extend);
+ }
+ }
+
+ this.contexts.push(rulesetNode.selectors);
+ },
+ visitRulesetOut: function (rulesetNode) {
+ if (!rulesetNode.root) {
+ this.contexts.length = this.contexts.length - 1;
+ }
+ },
+ visitMedia: function (mediaNode, visitArgs) {
+ mediaNode.allExtends = [];
+ this.allExtendsStack.push(mediaNode.allExtends);
+ },
+ visitMediaOut: function (mediaNode) {
+ this.allExtendsStack.length = this.allExtendsStack.length - 1;
+ },
+ visitDirective: function (directiveNode, visitArgs) {
+ directiveNode.allExtends = [];
+ this.allExtendsStack.push(directiveNode.allExtends);
+ },
+ visitDirectiveOut: function (directiveNode) {
+ this.allExtendsStack.length = this.allExtendsStack.length - 1;
+ }
+ };
+
+ tree.processExtendsVisitor = function() {
+ this._visitor = new tree.visitor(this);
+ };
+
+ tree.processExtendsVisitor.prototype = {
+ run: function(root) {
+ var extendFinder = new tree.extendFinderVisitor();
+ extendFinder.run(root);
+ if (!extendFinder.foundExtends) { return root; }
+ root.allExtends = root.allExtends.concat(this.doExtendChaining(root.allExtends, root.allExtends));
+ this.allExtendsStack = [root.allExtends];
+ return this._visitor.visit(root);
+ },
+ doExtendChaining: function (extendsList, extendsListTarget, iterationCount) {
+ //
+ // chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
+ // the selector we would do normally, but we are also adding an extend with the same target selector
+ // this means this new extend can then go and alter other extends
+ //
+ // this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
+ // this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
+ // we look at each selector at a time, as is done in visitRuleset
+
+ var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath, extend, targetExtend, newExtend;
+
+ iterationCount = iterationCount || 0;
+
+ //loop through comparing every extend with every target extend.
+ // a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
+ // e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
+ // and the second is the target.
+ // the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
+ // case when processing media queries
+ for(extendIndex = 0; extendIndex < extendsList.length; extendIndex++){
+ for(targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++){
+
+ extend = extendsList[extendIndex];
+ targetExtend = extendsListTarget[targetExtendIndex];
+
+ // look for circular references
+ if (this.inInheritanceChain(targetExtend, extend)) { continue; }
+
+ // find a match in the target extends self selector (the bit before :extend)
+ selectorPath = [targetExtend.selfSelectors[0]];
+ matches = extendVisitor.findMatch(extend, selectorPath);
+
+ if (matches.length) {
+
+ // we found a match, so for each self selector..
+ extend.selfSelectors.forEach(function(selfSelector) {
+
+ // process the extend as usual
+ newSelector = extendVisitor.extendSelector(matches, selectorPath, selfSelector);
+
+ // but now we create a new extend from it
+ newExtend = new(tree.Extend)(targetExtend.selector, targetExtend.option, 0);
+ newExtend.selfSelectors = newSelector;
+
+ // add the extend onto the list of extends for that selector
+ newSelector[newSelector.length-1].extendList = [newExtend];
+
+ // record that we need to add it.
+ extendsToAdd.push(newExtend);
+ newExtend.ruleset = targetExtend.ruleset;
+
+ //remember its parents for circular references
+ newExtend.parents = [targetExtend, extend];
+
+ // only process the selector once.. if we have :extend(.a,.b) then multiple
+ // extends will look at the same selector path, so when extending
+ // we know that any others will be duplicates in terms of what is added to the css
+ if (targetExtend.firstExtendOnThisSelectorPath) {
+ newExtend.firstExtendOnThisSelectorPath = true;
+ targetExtend.ruleset.paths.push(newSelector);
+ }
+ });
+ }
+ }
+ }
+
+ if (extendsToAdd.length) {
+ // try to detect circular references to stop a stack overflow.
+ // may no longer be needed.
+ this.extendChainCount++;
+ if (iterationCount > 100) {
+ var selectorOne = "{unable to calculate}";
+ var selectorTwo = "{unable to calculate}";
+ try
+ {
+ selectorOne = extendsToAdd[0].selfSelectors[0].toCSS();
+ selectorTwo = extendsToAdd[0].selector.toCSS();
+ }
+ catch(e) {}
+ throw {message: "extend circular reference detected. One of the circular extends is currently:"+selectorOne+":extend(" + selectorTwo+")"};
+ }
+
+ // now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
+ return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount+1));
+ } else {
+ return extendsToAdd;
+ }
+ },
+ inInheritanceChain: function (possibleParent, possibleChild) {
+ if (possibleParent === possibleChild) {
+ return true;
+ }
+ if (possibleChild.parents) {
+ if (this.inInheritanceChain(possibleParent, possibleChild.parents[0])) {
+ return true;
+ }
+ if (this.inInheritanceChain(possibleParent, possibleChild.parents[1])) {
+ return true;
+ }
+ }
+ return false;
+ },
+ visitRule: function (ruleNode, visitArgs) {
+ visitArgs.visitDeeper = false;
+ },
+ visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
+ visitArgs.visitDeeper = false;
+ },
+ visitSelector: function (selectorNode, visitArgs) {
+ visitArgs.visitDeeper = false;
+ },
+ visitRuleset: function (rulesetNode, visitArgs) {
+ if (rulesetNode.root) {
+ return;
+ }
+ var matches, pathIndex, extendIndex, allExtends = this.allExtendsStack[this.allExtendsStack.length-1], selectorsToAdd = [], extendVisitor = this, selectorPath;
+
+ // look at each selector path in the ruleset, find any extend matches and then copy, find and replace
+
+ for(extendIndex = 0; extendIndex < allExtends.length; extendIndex++) {
+ for(pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
+
+ selectorPath = rulesetNode.paths[pathIndex];
+
+ // extending extends happens initially, before the main pass
+ if (rulesetNode.extendOnEveryPath || selectorPath[selectorPath.length-1].extendList.length) { continue; }
+
+ matches = this.findMatch(allExtends[extendIndex], selectorPath);
+
+ if (matches.length) {
+
+ allExtends[extendIndex].selfSelectors.forEach(function(selfSelector) {
+ selectorsToAdd.push(extendVisitor.extendSelector(matches, selectorPath, selfSelector));
+ });
+ }
+ }
+ }
+ rulesetNode.paths = rulesetNode.paths.concat(selectorsToAdd);
+ },
+ findMatch: function (extend, haystackSelectorPath) {
+ //
+ // look through the haystack selector path to try and find the needle - extend.selector
+ // returns an array of selector matches that can then be replaced
+ //
+ var haystackSelectorIndex, hackstackSelector, hackstackElementIndex, haystackElement,
+ targetCombinator, i,
+ extendVisitor = this,
+ needleElements = extend.selector.elements,
+ potentialMatches = [], potentialMatch, matches = [];
+
+ // loop through the haystack elements
+ for(haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) {
+ hackstackSelector = haystackSelectorPath[haystackSelectorIndex];
+
+ for(hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) {
+
+ haystackElement = hackstackSelector.elements[hackstackElementIndex];
+
+ // if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
+ if (extend.allowBefore || (haystackSelectorIndex === 0 && hackstackElementIndex === 0)) {
+ potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0, initialCombinator: haystackElement.combinator});
+ }
+
+ for(i = 0; i < potentialMatches.length; i++) {
+ potentialMatch = potentialMatches[i];
+
+ // selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
+ // then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
+ // what the resulting combinator will be
+ targetCombinator = haystackElement.combinator.value;
+ if (targetCombinator === '' && hackstackElementIndex === 0) {
+ targetCombinator = ' ';
+ }
+
+ // if we don't match, null our match to indicate failure
+ if (!extendVisitor.isElementValuesEqual(needleElements[potentialMatch.matched].value, haystackElement.value) ||
+ (potentialMatch.matched > 0 && needleElements[potentialMatch.matched].combinator.value !== targetCombinator)) {
+ potentialMatch = null;
+ } else {
+ potentialMatch.matched++;
+ }
+
+ // if we are still valid and have finished, test whether we have elements after and whether these are allowed
+ if (potentialMatch) {
+ potentialMatch.finished = potentialMatch.matched === needleElements.length;
+ if (potentialMatch.finished &&
+ (!extend.allowAfter && (hackstackElementIndex+1 < hackstackSelector.elements.length || haystackSelectorIndex+1 < haystackSelectorPath.length))) {
+ potentialMatch = null;
+ }
+ }
+ // if null we remove, if not, we are still valid, so either push as a valid match or continue
+ if (potentialMatch) {
+ if (potentialMatch.finished) {
+ potentialMatch.length = needleElements.length;
+ potentialMatch.endPathIndex = haystackSelectorIndex;
+ potentialMatch.endPathElementIndex = hackstackElementIndex + 1; // index after end of match
+ potentialMatches.length = 0; // we don't allow matches to overlap, so start matching again
+ matches.push(potentialMatch);
+ }
+ } else {
+ potentialMatches.splice(i, 1);
+ i--;
+ }
+ }
+ }
+ }
+ return matches;
+ },
+ isElementValuesEqual: function(elementValue1, elementValue2) {
+ if (typeof elementValue1 === "string" || typeof elementValue2 === "string") {
+ return elementValue1 === elementValue2;
+ }
+ if (elementValue1 instanceof tree.Attribute) {
+ if (elementValue1.op !== elementValue2.op || elementValue1.key !== elementValue2.key) {
+ return false;
+ }
+ if (!elementValue1.value || !elementValue2.value) {
+ if (elementValue1.value || elementValue2.value) {
+ return false;
+ }
+ return true;
+ }
+ elementValue1 = elementValue1.value.value || elementValue1.value;
+ elementValue2 = elementValue2.value.value || elementValue2.value;
+ return elementValue1 === elementValue2;
+ }
+ elementValue1 = elementValue1.value;
+ elementValue2 = elementValue2.value;
+ if (elementValue1 instanceof tree.Selector) {
+ if (!(elementValue2 instanceof tree.Selector) || elementValue1.elements.length !== elementValue2.elements.length) {
+ return false;
+ }
+ for(var i = 0; i <elementValue1.elements.length; i++) {
+ if (elementValue1.elements[i].combinator.value !== elementValue2.elements[i].combinator.value) {
+ if (i !== 0 || (elementValue1.elements[i].combinator.value || ' ') !== (elementValue2.elements[i].combinator.value || ' ')) {
+ return false;
+ }
+ }
+ if (!this.isElementValuesEqual(elementValue1.elements[i].value, elementValue2.elements[i].value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ },
+ extendSelector:function (matches, selectorPath, replacementSelector) {
+
+ //for a set of matches, replace each match with the replacement selector
+
+ var currentSelectorPathIndex = 0,
+ currentSelectorPathElementIndex = 0,
+ path = [],
+ matchIndex,
+ selector,
+ firstElement,
+ match,
+ newElements;
+
+ for (matchIndex = 0; matchIndex < matches.length; matchIndex++) {
+ match = matches[matchIndex];
+ selector = selectorPath[match.pathIndex];
+ firstElement = new tree.Element(
+ match.initialCombinator,
+ replacementSelector.elements[0].value,
+ replacementSelector.elements[0].index,
+ replacementSelector.elements[0].currentFileInfo
+ );
+
+ if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) {
+ path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
+ currentSelectorPathElementIndex = 0;
+ currentSelectorPathIndex++;
+ }
+
+ newElements = selector.elements
+ .slice(currentSelectorPathElementIndex, match.index)
+ .concat([firstElement])
+ .concat(replacementSelector.elements.slice(1));
+
+ if (currentSelectorPathIndex === match.pathIndex && matchIndex > 0) {
+ path[path.length - 1].elements =
+ path[path.length - 1].elements.concat(newElements);
+ } else {
+ path = path.concat(selectorPath.slice(currentSelectorPathIndex, match.pathIndex));
+
+ path.push(new tree.Selector(
+ newElements
+ ));
+ }
+ currentSelectorPathIndex = match.endPathIndex;
+ currentSelectorPathElementIndex = match.endPathElementIndex;
+ if (currentSelectorPathElementIndex >= selectorPath[currentSelectorPathIndex].elements.length) {
+ currentSelectorPathElementIndex = 0;
+ currentSelectorPathIndex++;
+ }
+ }
+
+ if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) {
+ path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
+ currentSelectorPathIndex++;
+ }
+
+ path = path.concat(selectorPath.slice(currentSelectorPathIndex, selectorPath.length));
+
+ return path;
+ },
+ visitRulesetOut: function (rulesetNode) {
+ },
+ visitMedia: function (mediaNode, visitArgs) {
+ var newAllExtends = mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
+ newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, mediaNode.allExtends));
+ this.allExtendsStack.push(newAllExtends);
+ },
+ visitMediaOut: function (mediaNode) {
+ this.allExtendsStack.length = this.allExtendsStack.length - 1;
+ },
+ visitDirective: function (directiveNode, visitArgs) {
+ var newAllExtends = directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
+ newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, directiveNode.allExtends));
+ this.allExtendsStack.push(newAllExtends);
+ },
+ visitDirectiveOut: function (directiveNode) {
+ this.allExtendsStack.length = this.allExtendsStack.length - 1;
+ }
+ };
+
+})(require('./tree'));
+
+(function (tree) {
+
+ tree.sourceMapOutput = function (options) {
+ this._css = [];
+ this._rootNode = options.rootNode;
+ this._writeSourceMap = options.writeSourceMap;
+ this._contentsMap = options.contentsMap;
+ this._sourceMapFilename = options.sourceMapFilename;
+ this._outputFilename = options.outputFilename;
+ this._sourceMapBasepath = options.sourceMapBasepath;
+ this._sourceMapRootpath = options.sourceMapRootpath;
+ this._outputSourceFiles = options.outputSourceFiles;
+ this._sourceMapGeneratorConstructor = options.sourceMapGenerator || require("source-map").SourceMapGenerator;
+
+ if (this._sourceMapRootpath && this._sourceMapRootpath.charAt(this._sourceMapRootpath.length-1) !== '/') {
+ this._sourceMapRootpath += '/';
+ }
+
+ this._lineNumber = 0;
+ this._column = 0;
+ };
+
+ tree.sourceMapOutput.prototype.normalizeFilename = function(filename) {
+ if (this._sourceMapBasepath && filename.indexOf(this._sourceMapBasepath) === 0) {
+ filename = filename.substring(this._sourceMapBasepath.length);
+ if (filename.charAt(0) === '\\' || filename.charAt(0) === '/') {
+ filename = filename.substring(1);
+ }
+ }
+ return (this._sourceMapRootpath || "") + filename.replace(/\\/g, '/');
+ };
+
+ tree.sourceMapOutput.prototype.add = function(chunk, fileInfo, index, mapLines) {
+
+ //ignore adding empty strings
+ if (!chunk) {
+ return;
+ }
+
+ var lines,
+ sourceLines,
+ columns,
+ sourceColumns,
+ i;
+
+ if (fileInfo) {
+ var inputSource = this._contentsMap[fileInfo.filename].substring(0, index);
+ sourceLines = inputSource.split("\n");
+ sourceColumns = sourceLines[sourceLines.length-1];
+ }
+
+ lines = chunk.split("\n");
+ columns = lines[lines.length-1];
+
+ if (fileInfo) {
+ if (!mapLines) {
+ this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber + 1, column: this._column},
+ original: { line: sourceLines.length, column: sourceColumns.length},
+ source: this.normalizeFilename(fileInfo.filename)});
+ } else {
+ for(i = 0; i < lines.length; i++) {
+ this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber + i + 1, column: i === 0 ? this._column : 0},
+ original: { line: sourceLines.length + i, column: i === 0 ? sourceColumns.length : 0},
+ source: this.normalizeFilename(fileInfo.filename)});
+ }
+ }
+ }
+
+ if (lines.length === 1) {
+ this._column += columns.length;
+ } else {
+ this._lineNumber += lines.length - 1;
+ this._column = columns.length;
+ }
+
+ this._css.push(chunk);
+ };
+
+ tree.sourceMapOutput.prototype.isEmpty = function() {
+ return this._css.length === 0;
+ };
+
+ tree.sourceMapOutput.prototype.toCSS = function(env) {
+ this._sourceMapGenerator = new this._sourceMapGeneratorConstructor({ file: this._outputFilename, sourceRoot: null });
+
+ if (this._outputSourceFiles) {
+ for(var filename in this._contentsMap) {
+ this._sourceMapGenerator.setSourceContent(this.normalizeFilename(filename), this._contentsMap[filename]);
+ }
+ }
+
+ this._rootNode.genCSS(env, this);
+
+ if (this._css.length > 0) {
+ var sourceMapFilename,
+ sourceMapContent = JSON.stringify(this._sourceMapGenerator.toJSON());
+
+ if (this._sourceMapFilename) {
+ sourceMapFilename = this.normalizeFilename(this._sourceMapFilename);
+ }
+
+ if (this._writeSourceMap) {
+ this._writeSourceMap(sourceMapContent);
+ } else {
+ sourceMapFilename = "data:application/json," + encodeURIComponent(sourceMapContent);
+ }
+
+ if (sourceMapFilename) {
+ this._css.push("/*# sourceMappingURL=" + sourceMapFilename + " */");
+ }
+ }
+
+ return this._css.join('');
+ };
+
+})(require('./tree'));
+
+//
+// browser.js - client-side engine
+//
+/*global less, window, document, XMLHttpRequest, location */
+
+var isFileProtocol = /^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol);
+
+less.env = less.env || (location.hostname == '127.0.0.1' ||
+ location.hostname == '0.0.0.0' ||
+ location.hostname == 'localhost' ||
+ (location.port &&
+ location.port.length > 0) ||
+ isFileProtocol ? 'development'
+ : 'production');
+
+var logLevel = {
+ info: 2,
+ errors: 1,
+ none: 0
+};
+
+// The amount of logging in the javascript console.
+// 2 - Information and errors
+// 1 - Errors
+// 0 - None
+// Defaults to 2
+less.logLevel = typeof(less.logLevel) != 'undefined' ? less.logLevel : logLevel.info;
+
+// Load styles asynchronously (default: false)
+//
+// This is set to `false` by default, so that the body
+// doesn't start loading before the stylesheets are parsed.
+// Setting this to `true` can result in flickering.
+//
+less.async = less.async || false;
+less.fileAsync = less.fileAsync || false;
+
+// Interval between watch polls
+less.poll = less.poll || (isFileProtocol ? 1000 : 1500);
+
+//Setup user functions
+if (less.functions) {
+ for(var func in less.functions) {
+ less.tree.functions[func] = less.functions[func];
+ }
+}
+
+var dumpLineNumbers = /!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);
+if (dumpLineNumbers) {
+ less.dumpLineNumbers = dumpLineNumbers[1];
+}
+
+var typePattern = /^text\/(x-)?less$/;
+var cache = null;
+var fileCache = {};
+var varsPre = "";
+
+function log(str, level) {
+ if (less.env == 'development' && typeof(console) !== 'undefined' && less.logLevel >= level) {
+ console.log('less: ' + str);
+ }
+}
+
+function extractId(href) {
+ return href.replace(/^[a-z-]+:\/+?[^\/]+/, '' ) // Remove protocol & domain
+ .replace(/^\//, '' ) // Remove root /
+ .replace(/\.[a-zA-Z]+$/, '' ) // Remove simple extension
+ .replace(/[^\.\w-]+/g, '-') // Replace illegal characters
+ .replace(/\./g, ':'); // Replace dots with colons(for valid id)
+}
+
+function errorConsole(e, rootHref) {
+ var template = '{line} {content}';
+ var filename = e.filename || rootHref;
+ var errors = [];
+ var content = (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') +
+ " in " + filename + " ";
+
+ var errorline = function (e, i, classname) {
+ if (e.extract[i] !== undefined) {
+ errors.push(template.replace(/\{line\}/, (parseInt(e.line, 10) || 0) + (i - 1))
+ .replace(/\{class\}/, classname)
+ .replace(/\{content\}/, e.extract[i]));
+ }
+ };
+
+ if (e.extract) {
+ errorline(e, 0, '');
+ errorline(e, 1, 'line');
+ errorline(e, 2, '');
+ content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':\n' +
+ errors.join('\n');
+ } else if (e.stack) {
+ content += e.stack;
+ }
+ log(content, logLevel.errors);
+}
+
+function createCSS(styles, sheet, lastModified) {
+ // Strip the query-string
+ var href = sheet.href || '';
+
+ // If there is no title set, use the filename, minus the extension
+ var id = 'less:' + (sheet.title || extractId(href));
+
+ // If this has already been inserted into the DOM, we may need to replace it
+ var oldCss = document.getElementById(id);
+ var keepOldCss = false;
+
+ // Create a new stylesheet node for insertion or (if necessary) replacement
+ var css = document.createElement('style');
+ css.setAttribute('type', 'text/css');
+ if (sheet.media) {
+ css.setAttribute('media', sheet.media);
+ }
+ css.id = id;
+
+ if (css.styleSheet) { // IE
+ try {
+ css.styleSheet.cssText = styles;
+ } catch (e) {
+ throw new(Error)("Couldn't reassign styleSheet.cssText.");
+ }
+ } else {
+ css.appendChild(document.createTextNode(styles));
+
+ // If new contents match contents of oldCss, don't replace oldCss
+ keepOldCss = (oldCss !== null && oldCss.childNodes.length > 0 && css.childNodes.length > 0 &&
+ oldCss.firstChild.nodeValue === css.firstChild.nodeValue);
+ }
+
+ var head = document.getElementsByTagName('head')[0];
+
+ // If there is no oldCss, just append; otherwise, only append if we need
+ // to replace oldCss with an updated stylesheet
+ if (oldCss === null || keepOldCss === false) {
+ var nextEl = sheet && sheet.nextSibling || null;
+ if (nextEl) {
+ nextEl.parentNode.insertBefore(css, nextEl);
+ } else {
+ head.appendChild(css);
+ }
+ }
+ if (oldCss && keepOldCss === false) {
+ oldCss.parentNode.removeChild(oldCss);
+ }
+
+ // Don't update the local store if the file wasn't modified
+ if (lastModified && cache) {
+ log('saving ' + href + ' to cache.', logLevel.info);
+ try {
+ cache.setItem(href, styles);
+ cache.setItem(href + ':timestamp', lastModified);
+ } catch(e) {
+ //TODO - could do with adding more robust error handling
+ log('failed to save', logLevel.errors);
+ }
+ }
+}
+
+function errorHTML(e, rootHref) {
+ var id = 'less-error-message:' + extractId(rootHref || "");
+ var template = '<li><label>{line}</label><pre class="{class}">{content}</pre></li>';
+ var elem = document.createElement('div'), timer, content, errors = [];
+ var filename = e.filename || rootHref;
+ var filenameNoPath = filename.match(/([^\/]+(\?.*)?)$/)[1];
+
+ elem.id = id;
+ elem.className = "less-error-message";
+
+ content = '<h3>' + (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') +
+ '</h3>' + '<p>in <a href="' + filename + '">' + filenameNoPath + "</a> ";
+
+ var errorline = function (e, i, classname) {
+ if (e.extract[i] !== undefined) {
+ errors.push(template.replace(/\{line\}/, (parseInt(e.line, 10) || 0) + (i - 1))
+ .replace(/\{class\}/, classname)
+ .replace(/\{content\}/, e.extract[i]));
+ }
+ };
+
+ if (e.extract) {
+ errorline(e, 0, '');
+ errorline(e, 1, 'line');
+ errorline(e, 2, '');
+ content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':</p>' +
+ '<ul>' + errors.join('') + '</ul>';
+ } else if (e.stack) {
+ content += '<br/>' + e.stack.split('\n').slice(1).join('<br/>');
+ }
+ elem.innerHTML = content;
+
+ // CSS for error messages
+ createCSS([
+ '.less-error-message ul, .less-error-message li {',
+ 'list-style-type: none;',
+ 'margin-right: 15px;',
+ 'padding: 4px 0;',
+ 'margin: 0;',
+ '}',
+ '.less-error-message label {',
+ 'font-size: 12px;',
+ 'margin-right: 15px;',
+ 'padding: 4px 0;',
+ 'color: #cc7777;',
+ '}',
+ '.less-error-message pre {',
+ 'color: #dd6666;',
+ 'padding: 4px 0;',
+ 'margin: 0;',
+ 'display: inline-block;',
+ '}',
+ '.less-error-message pre.line {',
+ 'color: #ff0000;',
+ '}',
+ '.less-error-message h3 {',
+ 'font-size: 20px;',
+ 'font-weight: bold;',
+ 'padding: 15px 0 5px 0;',
+ 'margin: 0;',
+ '}',
+ '.less-error-message a {',
+ 'color: #10a',
+ '}',
+ '.less-error-message .error {',
+ 'color: red;',
+ 'font-weight: bold;',
+ 'padding-bottom: 2px;',
+ 'border-bottom: 1px dashed red;',
+ '}'
+ ].join('\n'), { title: 'error-message' });
+
+ elem.style.cssText = [
+ "font-family: Arial, sans-serif",
+ "border: 1px solid #e00",
+ "background-color: #eee",
+ "border-radius: 5px",
+ "-webkit-border-radius: 5px",
+ "-moz-border-radius: 5px",
+ "color: #e00",
+ "padding: 15px",
+ "margin-bottom: 15px"
+ ].join(';');
+
+ if (less.env == 'development') {
+ timer = setInterval(function () {
+ if (document.body) {
+ if (document.getElementById(id)) {
+ document.body.replaceChild(elem, document.getElementById(id));
+ } else {
+ document.body.insertBefore(elem, document.body.firstChild);
+ }
+ clearInterval(timer);
+ }
+ }, 10);
+ }
+}
+
+function error(e, rootHref) {
+ if (!less.errorReporting || less.errorReporting === "html") {
+ errorHTML(e, rootHref);
+ } else if (less.errorReporting === "console") {
+ errorConsole(e, rootHref);
+ } else if (typeof less.errorReporting === 'function') {
+ less.errorReporting("add", e, rootHref);
+ }
+}
+
+function removeErrorHTML(path) {
+ var node = document.getElementById('less-error-message:' + extractId(path));
+ if (node) {
+ node.parentNode.removeChild(node);
+ }
+}
+
+function removeErrorConsole(path) {
+ //no action
+}
+
+function removeError(path) {
+ if (!less.errorReporting || less.errorReporting === "html") {
+ removeErrorHTML(path);
+ } else if (less.errorReporting === "console") {
+ removeErrorConsole(path);
+ } else if (typeof less.errorReporting === 'function') {
+ less.errorReporting("remove", path);
+ }
+}
+
+function loadStyles(newVars) {
+ var styles = document.getElementsByTagName('style'),
+ style;
+ for (var i = 0; i < styles.length; i++) {
+ style = styles[i];
+ if (style.type.match(typePattern)) {
+ var env = new less.tree.parseEnv(less),
+ lessText = style.innerHTML || '';
+ env.filename = document.location.href.replace(/#.*$/, '');
+
+ if (newVars || varsPre) {
+ env.useFileCache = true;
+
+ lessText = varsPre + lessText;
+
+ if (newVars) {
+ lessText += "\n" + newVars;
+ }
+ }
+
+ /*jshint loopfunc:true */
+ // use closure to store current value of i
+ var callback = (function(style) {
+ return function (e, cssAST) {
+ if (e) {
+ return error(e, "inline");
+ }
+ var css = cssAST.toCSS(less);
+ style.type = 'text/css';
+ if (style.styleSheet) {
+ style.styleSheet.cssText = css;
+ } else {
+ style.innerHTML = css;
+ }
+ };
+ })(style);
+ new(less.Parser)(env).parse(lessText, callback);
+ }
+ }
+}
+
+function extractUrlParts(url, baseUrl) {
+ // urlParts[1] = protocol&hostname || /
+ // urlParts[2] = / if path relative to host base
+ // urlParts[3] = directories
+ // urlParts[4] = filename
+ // urlParts[5] = parameters
+
+ var urlPartsRegex = /^((?:[a-z-]+:)?\/+?(?:[^\/\?#]*\/)|([\/\\]))?((?:[^\/\\\?#]*[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/i,
+ urlParts = url.match(urlPartsRegex),
+ returner = {}, directories = [], i, baseUrlParts;
+
+ if (!urlParts) {
+ throw new Error("Could not parse sheet href - '"+url+"'");
+ }
+
+ // Stylesheets in IE don't always return the full path
+ if (!urlParts[1] || urlParts[2]) {
+ baseUrlParts = baseUrl.match(urlPartsRegex);
+ if (!baseUrlParts) {
+ throw new Error("Could not parse page url - '"+baseUrl+"'");
+ }
+ urlParts[1] = urlParts[1] || baseUrlParts[1] || "";
+ if (!urlParts[2]) {
+ urlParts[3] = baseUrlParts[3] + urlParts[3];
+ }
+ }
+
+ if (urlParts[3]) {
+ directories = urlParts[3].replace(/\\/g, "/").split("/");
+
+ // extract out . before .. so .. doesn't absorb a non-directory
+ for(i = 0; i < directories.length; i++) {
+ if (directories[i] === ".") {
+ directories.splice(i, 1);
+ i -= 1;
+ }
+ }
+
+ for(i = 0; i < directories.length; i++) {
+ if (directories[i] === ".." && i > 0) {
+ directories.splice(i-1, 2);
+ i -= 2;
+ }
+ }
+ }
+
+ returner.hostPart = urlParts[1];
+ returner.directories = directories;
+ returner.path = urlParts[1] + directories.join("/");
+ returner.fileUrl = returner.path + (urlParts[4] || "");
+ returner.url = returner.fileUrl + (urlParts[5] || "");
+ return returner;
+}
+
+function pathDiff(url, baseUrl) {
+ // diff between two paths to create a relative path
+
+ var urlParts = extractUrlParts(url),
+ baseUrlParts = extractUrlParts(baseUrl),
+ i, max, urlDirectories, baseUrlDirectories, diff = "";
+ if (urlParts.hostPart !== baseUrlParts.hostPart) {
+ return "";
+ }
+ max = Math.max(baseUrlParts.directories.length, urlParts.directories.length);
+ for(i = 0; i < max; i++) {
+ if (baseUrlParts.directories[i] !== urlParts.directories[i]) { break; }
+ }
+ baseUrlDirectories = baseUrlParts.directories.slice(i);
+ urlDirectories = urlParts.directories.slice(i);
+ for(i = 0; i < baseUrlDirectories.length-1; i++) {
+ diff += "../";
+ }
+ for(i = 0; i < urlDirectories.length-1; i++) {
+ diff += urlDirectories[i] + "/";
+ }
+ return diff;
+}
+
+function getXMLHttpRequest() {
+ if (window.XMLHttpRequest) {
+ return new XMLHttpRequest();
+ } else {
+ try {
+ /*global ActiveXObject */
+ return new ActiveXObject("MSXML2.XMLHTTP.3.0");
+ } catch (e) {
+ log("browser doesn't support AJAX.", logLevel.errors);
+ return null;
+ }
+ }
+}
+
+function doXHR(url, type, callback, errback) {
+ var xhr = getXMLHttpRequest();
+ var async = isFileProtocol ? less.fileAsync : less.async;
+
+ if (typeof(xhr.overrideMimeType) === 'function') {
+ xhr.overrideMimeType('text/css');
+ }
+ log("XHR: Getting '" + url + "'", logLevel.info);
+ xhr.open('GET', url, async);
+ xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5');
+ xhr.send(null);
+
+ function handleResponse(xhr, callback, errback) {
+ if (xhr.status >= 200 && xhr.status < 300) {
+ callback(xhr.responseText,
+ xhr.getResponseHeader("Last-Modified"));
+ } else if (typeof(errback) === 'function') {
+ errback(xhr.status, url);
+ }
+ }
+
+ if (isFileProtocol && !less.fileAsync) {
+ if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) {
+ callback(xhr.responseText);
+ } else {
+ errback(xhr.status, url);
+ }
+ } else if (async) {
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState == 4) {
+ handleResponse(xhr, callback, errback);
+ }
+ };
+ } else {
+ handleResponse(xhr, callback, errback);
+ }
+}
+
+function loadFile(originalHref, currentFileInfo, callback, env, newVars) {
+
+ if (currentFileInfo && currentFileInfo.currentDirectory && !/^([a-z-]+:)?\//.test(originalHref)) {
+ originalHref = currentFileInfo.currentDirectory + originalHref;
+ }
+
+ // sheet may be set to the stylesheet for the initial load or a collection of properties including
+ // some env variables for imports
+ var hrefParts = extractUrlParts(originalHref, window.location.href);
+ var href = hrefParts.url;
+ var newFileInfo = {
+ currentDirectory: hrefParts.path,
+ filename: href
+ };
+
+ if (currentFileInfo) {
+ newFileInfo.entryPath = currentFileInfo.entryPath;
+ newFileInfo.rootpath = currentFileInfo.rootpath;
+ newFileInfo.rootFilename = currentFileInfo.rootFilename;
+ newFileInfo.relativeUrls = currentFileInfo.relativeUrls;
+ } else {
+ newFileInfo.entryPath = hrefParts.path;
+ newFileInfo.rootpath = less.rootpath || hrefParts.path;
+ newFileInfo.rootFilename = href;
+ newFileInfo.relativeUrls = env.relativeUrls;
+ }
+
+ if (newFileInfo.relativeUrls) {
+ if (env.rootpath) {
+ newFileInfo.rootpath = extractUrlParts(env.rootpath + pathDiff(hrefParts.path, newFileInfo.entryPath)).path;
+ } else {
+ newFileInfo.rootpath = hrefParts.path;
+ }
+ }
+
+ if (env.useFileCache && fileCache[href]) {
+ try {
+ var lessText = fileCache[href];
+ if (newVars) {
+ lessText += "\n" + newVars;
+ }
+ callback(null, lessText, href, newFileInfo, { lastModified: new Date() });
+ } catch (e) {
+ callback(e, null, href);
+ }
+ return;
+ }
+
+ doXHR(href, env.mime, function (data, lastModified) {
+ data = varsPre + data;
+
+ // per file cache
+ fileCache[href] = data;
+
+ // Use remote copy (re-parse)
+ try {
+ callback(null, data, href, newFileInfo, { lastModified: lastModified });
+ } catch (e) {
+ callback(e, null, href);
+ }
+ }, function (status, url) {
+ callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, href);
+ });
+}
+
+function loadStyleSheet(sheet, callback, reload, remaining, newVars) {
+
+ var env = new less.tree.parseEnv(less);
+ env.mime = sheet.type;
+
+ if (newVars || varsPre) {
+ env.useFileCache = true;
+ }
+
+ loadFile(sheet.href, null, function(e, data, path, newFileInfo, webInfo) {
+
+ if (webInfo) {
+ webInfo.remaining = remaining;
+
+ var css = cache && cache.getItem(path),
+ timestamp = cache && cache.getItem(path + ':timestamp');
+
+ if (!reload && timestamp && webInfo.lastModified &&
+ (new(Date)(webInfo.lastModified).valueOf() ===
+ new(Date)(timestamp).valueOf())) {
+ // Use local copy
+ createCSS(css, sheet);
+ webInfo.local = true;
+ callback(null, null, data, sheet, webInfo, path);
+ return;
+ }
+ }
+
+ //TODO add tests around how this behaves when reloading
+ removeError(path);
+
+ if (data) {
+ env.currentFileInfo = newFileInfo;
+ new(less.Parser)(env).parse(data, function (e, root) {
+ if (e) { return callback(e, null, null, sheet); }
+ try {
+ callback(e, root, data, sheet, webInfo, path);
+ } catch (e) {
+ callback(e, null, null, sheet);
+ }
+ });
+ } else {
+ callback(e, null, null, sheet, webInfo, path);
+ }
+ }, env, newVars);
+}
+
+function loadStyleSheets(callback, reload, newVars) {
+ for (var i = 0; i < less.sheets.length; i++) {
+ loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), newVars);
+ }
+}
+
+function initRunningMode(){
+ if (less.env === 'development') {
+ less.optimization = 0;
+ less.watchTimer = setInterval(function () {
+ if (less.watchMode) {
+ loadStyleSheets(function (e, root, _, sheet, env) {
+ if (e) {
+ error(e, sheet.href);
+ } else if (root) {
+ createCSS(root.toCSS(less), sheet, env.lastModified);
+ }
+ });
+ }
+ }, less.poll);
+ } else {
+ less.optimization = 3;
+ }
+}
+
+function serializeVars(vars) {
+ var s = "";
+
+ for (var name in vars) {
+ s += ((name.slice(0,1) === '@')? '' : '@') + name +': '+
+ ((vars[name].slice(-1) === ';')? vars[name] : vars[name] +';');
+ }
+
+ return s;
+}
+
+
+//
+// Watch mode
+//
+less.watch = function () {
+ if (!less.watchMode ){
+ less.env = 'development';
+ initRunningMode();
+ }
+ return this.watchMode = true;
+};
+
+less.unwatch = function () {clearInterval(less.watchTimer); return this.watchMode = false; };
+
+if (/!watch/.test(location.hash)) {
+ less.watch();
+}
+
+if (less.env != 'development') {
+ try {
+ cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage;
+ } catch (_) {}
+}
+
+//
+// Get all <link> tags with the 'rel' attribute set to "stylesheet/less"
+//
+var links = document.getElementsByTagName('link');
+
+less.sheets = [];
+
+for (var i = 0; i < links.length; i++) {
+ if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
+ (links[i].type.match(typePattern)))) {
+ less.sheets.push(links[i]);
+ }
+}
+
+//
+// With this function, it's possible to alter variables and re-render
+// CSS without reloading less-files
+//
+less.modifyVars = function(record) {
+ less.refresh(false, serializeVars(record));
+};
+
+less.refresh = function (reload, newVars) {
+ var startTime, endTime;
+ startTime = endTime = new Date();
+
+ loadStyleSheets(function (e, root, _, sheet, env) {
+ if (e) {
+ return error(e, sheet.href);
+ }
+ if (env.local) {
+ log("loading " + sheet.href + " from cache.", logLevel.info);
+ } else {
+ log("parsed " + sheet.href + " successfully.", logLevel.info);
+ createCSS(root.toCSS(less), sheet, env.lastModified);
+ }
+ log("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms', logLevel.info);
+ if (env.remaining === 0) {
+ log("css generated in " + (new Date() - startTime) + 'ms', logLevel.info);
+ }
+ endTime = new Date();
+ }, reload, newVars);
+
+ loadStyles(newVars);
+};
+
+if (less.globalVars) {
+ varsPre = serializeVars(less.globalVars) + "\n";
+}
+
+less.refreshStyles = loadStyles;
+
+less.Parser.fileLoader = loadFile;
+
+less.refresh(less.env === 'development');
+
+// amd.js
+//
+// Define Less as an AMD module.
+if (typeof define === "function" && define.amd) {
+ define(function () { return less; } );
+}
+
+})(window);
\ No newline at end of file
--- /dev/null
+.a {
+ prop: (3 / #fff);
+}
\ No newline at end of file
--- /dev/null
+less: OperationError: Can't substract or divide a color from a number in {pathhref}console-errors/test-error.less on line null, column 0:
+1 prop: (3 / #fff);
--- /dev/null
+.test {
+ color: @global-var;
+}
--- /dev/null
+@import "modify-this.css";
+.modify {
+ my-url: url("a.png");
+}
\ No newline at end of file
--- /dev/null
+@import "modify-again.css";
+.modify {
+ my-url: url("b.png");
+}
\ No newline at end of file
--- /dev/null
+@var2: blue;
+.testisimported {
+ color: gainsboro;
+}
\ No newline at end of file
--- /dev/null
+@import "imports/simple2";
+@var1: red;
+.test {
+ color1: @var1;
+ color2: @var2;
+}
\ No newline at end of file
--- /dev/null
+@import ".././imports/urls.less";
+@import "http://localhost:8081/test/browser/less/imports/urls2.less";
+@font-face {
+ src: local(Futura-Medium),
+ url(fonts.svg#MyGeometricModern) format("svg");
+}
+#shorthands {
+ background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
+}
+#misc {
+ background-image: url(images/image.jpg);
+}
+#data-uri {
+ background: url(data:image/png;charset=utf-8;base64,
+ kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
+ k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
+ kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
+ background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
+ background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
+}
+
+#svg-data-uri {
+ background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
+}
+
+.comma-delimited {
+ background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);
+}
+.values {
+ @a: 'Trebuchet';
+ url: url(@a);
+}
--- /dev/null
+@import "../imports/urls.less";
+@import "http://localhost:8081/test/browser/less/imports/urls2.less";
+@font-face {
+ src: local(Futura-Medium),
+ url(fonts.svg#MyGeometricModern) format("svg");
+}
+#shorthands {
+ background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
+}
+#misc {
+ background-image: url(images/image.jpg);
+}
+#data-uri {
+ background: url(data:image/png;charset=utf-8;base64,
+ kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
+ k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
+ kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
+ background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
+ background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
+}
+
+#svg-data-uri {
+ background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
+}
+
+.comma-delimited {
+ background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);
+}
+.values {
+ @a: 'Trebuchet';
+ url: url(@a);
+}
--- /dev/null
+@import "../imports/urls.less";
+@import "http://localhost:8081/test/browser/less/imports/urls2.less";
+@font-face {
+ src: local(Futura-Medium),
+ url(fonts.svg#MyGeometricModern) format("svg");
+}
+#shorthands {
+ background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
+}
+#misc {
+ background-image: url(images/image.jpg);
+}
+#data-uri {
+ background: url(data:image/png;charset=utf-8;base64,
+ kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
+ k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
+ kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
+ background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
+ background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
+}
+
+#svg-data-uri {
+ background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
+}
+
+.comma-delimited {
+ background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);
+}
+.values {
+ @a: 'Trebuchet';
+ url: url(@a);
+}
--- /dev/null
+@import "imports/urls.less";
+@import "http://localhost:8081/test/browser/less/imports/urls2.less";
+@font-face {
+ src: local(Futura-Medium),
+ url(fonts.svg#MyGeometricModern) format("svg");
+}
+#shorthands {
+ background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
+}
+#misc {
+ background-image: url(images/image.jpg);
+}
+#data-uri {
+ background: url(data:image/png;charset=utf-8;base64,
+ kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
+ k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
+ kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
+ background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
+ background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
+}
+
+#svg-data-uri {
+ background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
+}
+
+.comma-delimited {
+ background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);
+}
+.values {
+ @a: 'Trebuchet';
+ url: url(@a);
+}
+#data-uri {
+ uri: data-uri('image/jpeg;base64', '../../data/image.jpg');
+}
+
+#data-uri-guess {
+ uri: data-uri('../../data/image.jpg');
+}
+
+#data-uri-ascii {
+ uri-1: data-uri('text/html', '../../data/page.html');
+ uri-2: data-uri('../../data/page.html');
+}
+
+#data-uri-toobig {
+ uri: data-uri('../../data/data-uri-fail.png');
+}
+#svg-functions {
+ background-image: svg-gradient(to bottom, black, white);
+ background-image: svg-gradient(to bottom, black, orange 3%, white);
+ @green_5: green 5%;
+ @orange_percentage: 3%;
+ @orange_color: orange;
+ background-image: svg-gradient(to bottom, (mix(black, white) + #444) 1%, @orange_color @orange_percentage, ((@green_5)), white 95%);
+}
--- /dev/null
+var webpage = require('webpage');
+var server = require('webserver').create();
+var system = require('system');
+var fs = require('fs');
+var host;
+var port = 8081;
+
+var listening = server.listen(port, function(request, response) {
+ //console.log("Requested " + request.url);
+
+ var filename = ("test/" + request.url.slice(1)).replace(/[\\\/]/g, fs.separator);
+
+ if (!fs.exists(filename) || !fs.isFile(filename)) {
+ response.statusCode = 404;
+ response.write("<html><head></head><body><h1>File Not Found</h1><h2>File:" + filename + "</h2></body></html>");
+ response.close();
+ return;
+ }
+
+ // we set the headers here
+ response.statusCode = 200;
+ response.headers = {
+ "Cache": "no-cache",
+ "Content-Type": "text/html"
+ };
+
+ response.write(fs.read(filename));
+
+ response.close();
+});
+if (!listening) {
+ console.log("could not create web server listening on port " + port);
+ phantom.exit();
+}
+
+/**
+ * Wait until the test condition is true or a timeout occurs. Useful for waiting
+ * on a server response or for a ui change (fadeIn, etc.) to occur.
+ *
+ * @param testFx javascript condition that evaluates to a boolean,
+ * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
+ * as a callback function.
+ * @param onReady what to do when testFx condition is fulfilled,
+ * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
+ * as a callback function.
+ * @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
+ * @param timeOutErrorMessage the error message if time out occurs
+ */
+
+function waitFor(testFx, onReady, timeOutMillis, timeOutErrorMessage) {
+ var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 10001, //< Default Max Timeout is 10s
+ start = new Date().getTime(),
+ condition = false,
+ interval = setInterval(function() {
+ if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) {
+ // If not time-out yet and condition not yet fulfilled
+ condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
+ } else {
+ if (!condition) {
+ // If condition still not fulfilled (timeout but condition is 'false')
+ console.log(timeOutErrorMessage || "'waitFor()' timeout");
+ phantom.exit(1);
+ } else {
+ // Condition fulfilled (timeout and/or condition is 'true')
+ typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
+ clearInterval(interval); //< Stop this interval
+ }
+ }
+ }, 100); //< repeat check every 100ms
+}
+
+function testPage(url) {
+ var page = webpage.create();
+ page.open(url, function(status) {
+ if (status !== "success") {
+ console.log("Unable to access network - " + status);
+ phantom.exit();
+ } else {
+ waitFor(function() {
+ return page.evaluate(function() {
+ return document.body && document.body.querySelector &&
+ document.body.querySelector('.symbolSummary .pending') === null &&
+ document.body.querySelector('.results') !== null;
+ });
+ }, function() {
+ page.onConsoleMessage = function(msg) {
+ console.log(msg);
+ };
+ var exitCode = page.evaluate(function() {
+ console.log('');
+ console.log(document.body.querySelector('.description').innerText);
+ var list = document.body.querySelectorAll('.results > #details > .specDetail.failed');
+ if (list && list.length > 0) {
+ console.log('');
+ console.log(list.length + ' test(s) FAILED:');
+ for (var i = 0; i < list.length; ++i) {
+ var el = list[i],
+ desc = el.querySelector('.description'),
+ msg = el.querySelector('.resultMessage.fail');
+ console.log('');
+ console.log(desc.innerText);
+ console.log(msg.innerText);
+ console.log('');
+ }
+ return 1;
+ } else {
+ console.log(document.body.querySelector('.alert > .passingAlert.bar').innerText);
+ return 0;
+ }
+ });
+ testFinished(exitCode);
+ },
+ 10000, // 10 second timeout
+ "Test failed waiting for jasmine results on page: " + url);
+ }
+ });
+}
+
+function scanDirectory(path, regex) {
+ var files = [];
+ fs.list(path).forEach(function(file) {
+ if (file.match(regex)) {
+ files.push(file);
+ }
+ });
+ return files;
+}
+
+var totalTests = 0,
+ totalFailed = 0,
+ totalDone = 0;
+
+function testFinished(failed) {
+ if (failed) {
+ totalFailed++;
+ }
+ totalDone++;
+ if (totalDone === totalTests) {
+ phantom.exit(totalFailed > 0 ? 1 : 0);
+ }
+}
+
+if (system.args.length != 2 && system.args[1] != "--no-tests") {
+ var files = scanDirectory("test/browser/", /^test-runner-.+\.htm$/);
+ totalTests = files.length;
+ console.log("found " + files.length + " tests");
+ files.forEach(function(file) {
+ testPage("http://localhost:8081/browser/" + file);
+ });
+}
\ No newline at end of file
--- /dev/null
+var less = {};
+
+// There originally run inside describe method. However, since they have not
+// been inside it, they run at jasmine compile time (not runtime). It all
+// worked cause less.js was in async mode and custom phantom runner had
+// different setup then grunt-contrib-jasmine. They have been created before
+// less.js run, even as they have been defined in spec.
+
+// test inline less in style tags by grabbing an assortment of less files and doing `@import`s
+var testFiles = ['charsets', 'colors', 'comments', 'css-3', 'strings', 'media', 'mixins'],
+ testSheets = [];
+
+// setup style tags with less and link tags pointing to expected css output
+for (var i = 0; i < testFiles.length; i++) {
+ var file = testFiles[i],
+ lessPath = '/test/less/' + file + '.less',
+ cssPath = '/test/css/' + file + '.css',
+ lessStyle = document.createElement('style'),
+ cssLink = document.createElement('link'),
+ lessText = '@import "' + lessPath + '";';
+
+ lessStyle.type = 'text/less';
+ lessStyle.id = file;
+ lessStyle.href = file;
+
+ if (lessStyle.styleSheet) {
+ lessStyle.styleSheet.cssText = lessText;
+ } else {
+ lessStyle.innerHTML = lessText;
+ }
+
+ cssLink.rel = 'stylesheet';
+ cssLink.type = 'text/css';
+ cssLink.href = cssPath;
+ cssLink.id = 'expected-' + file;
+
+ var head = document.getElementsByTagName('head')[0];
+
+ head.appendChild(lessStyle);
+ head.appendChild(cssLink);
+ testSheets[i] = lessStyle;
+}
\ No newline at end of file
--- /dev/null
+describe("less.js browser behaviour", function() {
+ testLessEqualsInDocument();
+
+ it("has some log messages", function() {
+ expect(logMessages.length).toBeGreaterThan(0);
+ });
+
+ for (var i = 0; i < testFiles.length; i++) {
+ var sheet = testSheets[i];
+ testSheet(sheet);
+ }
+});
--- /dev/null
+less.errorReporting = 'console';
+
+describe("less.js error reporting console test", function() {
+ testLessErrorsInDocument(true);
+});
\ No newline at end of file
--- /dev/null
+var less = {
+ strictUnits: true,
+ strictMath: true
+};
+
--- /dev/null
+describe("less.js error tests", function() {
+ testLessErrorsInDocument();
+});
+
--- /dev/null
+var less = {};
+less.globalVars = {
+ "@global-var": "red"
+};
--- /dev/null
+describe("less.js global vars", function() {
+ testLessEqualsInDocument();
+});
--- /dev/null
+var less = {};
+less.strictMath = false;
+less.strictUnits = false;
+
--- /dev/null
+describe("less.js legacy tests", function() {
+ testLessEqualsInDocument();
+});
--- /dev/null
+var less = {};
+less.strictMath = true;
+less.functions = {
+ add: function(a, b) {
+ return new(less.tree.Dimension)(a.value + b.value);
+ },
+ increment: function(a) {
+ return new(less.tree.Dimension)(a.value + 1);
+ },
+ _color: function(str) {
+ if (str.value === "evil red") {
+ return new(less.tree.Color)("600");
+ }
+ }
+};
\ No newline at end of file
--- /dev/null
+describe("less.js main tests", function() {
+ testLessEqualsInDocument();
+});
--- /dev/null
+/* exported less */
+var less = {};
\ No newline at end of file
--- /dev/null
+var alreadyRun = false;
+
+describe("less.js modify vars", function() {
+ beforeEach(function() {
+ // simulating "setUp" or "beforeAll" method
+ var lessOutputObj;
+ if (alreadyRun)
+ return;
+
+ alreadyRun = true;
+
+ // wait until the sheet is compiled first time
+ waitsFor(function() {
+ lessOutputObj = document.getElementById("less:test-less-simple");
+ return lessOutputObj !== null;
+ }, "first generation of less:test-less-simple", 7000);
+
+ // modify variables
+ runs(function() {
+ lessOutputObj.type = "not compiled yet";
+ less.modifyVars({
+ var1: "green",
+ var2: "purple"
+ });
+ });
+
+ // wait until variables are modified
+ waitsFor(function() {
+ lessOutputObj = document.getElementById("less:test-less-simple");
+ return lessOutputObj !== null && lessOutputObj.type === "text/css";
+ }, "second generation of less:test-less-simple", 7000);
+
+ });
+
+ testLessEqualsInDocument();
+ it("Should log only 2 XHR requests", function() {
+ var xhrLogMessages = logMessages.filter(function(item) {
+ return (/XHR: Getting '/).test(item);
+ });
+ expect(xhrLogMessages.length).toEqual(2);
+ });
+});
\ No newline at end of file
--- /dev/null
+var less = {};
+
+less.strictUnits = true;
+less.javascriptEnabled = false;
--- /dev/null
+describe("less.js javascript disabled error tests", function() {
+ testLessErrorsInDocument();
+});
+
--- /dev/null
+var less = {};
+less.env = "production";
+
--- /dev/null
+describe("less.js production behaviour", function() {
+ it("doesn't log any messages", function() {
+ expect(logMessages.length).toEqual(0);
+ });
+});
--- /dev/null
+var less = {};
+less.relativeUrls = true;
+
--- /dev/null
+describe("less.js browser test - relative url's", function() {
+ testLessEqualsInDocument();
+});
--- /dev/null
+var less = {};
+less.rootpath = "https://www.github.com/";
+
--- /dev/null
+var less = {};
+less.rootpath = "https://www.github.com/cloudhead/less.js/";
+less.relativeUrls = true;
+
--- /dev/null
+describe("less.js browser test - rootpath and relative url's", function() {
+ testLessEqualsInDocument();
+});
--- /dev/null
+describe("less.js browser test - rootpath url's", function() {
+ testLessEqualsInDocument();
+});
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Jasmine Spec Runner</title>
+
+ <!-- generate script tags for tests -->
+ <% var generateScriptTags = function(allScripts) { allScripts.forEach(function(script){ %>
+ <script src="<%= script %>"></script>
+ <% }); }; %>
+
+ <!-- for each test, generate CSS/LESS link tags -->
+ <% scripts.src.forEach(function(fullLessName) {
+ var pathParts = fullLessName.split('/');
+ var fullCssName = fullLessName.replace(/less/g, 'css');
+ var lessName = pathParts[pathParts.length - 1];
+ var name = lessName.split('.')[0]; %>
+ <!-- the tags to be generated -->
+ <link id="original-less:test-less-<%= name %>" title="test-less-<%= name %>" rel="stylesheet/less" type="text/css" href="<%= fullLessName %>">
+ <link id="expected-less:test-less-<%= name %>" rel="stylesheet" type="text/css" href="<%= fullCssName %>">
+ <% }); %>
+
+ <!-- generate grunt-contrib-jasmine link tags -->
+ <% css.forEach(function(style){ %>
+ <link rel="stylesheet" type="text/css" href="<%= style %>">
+ <% }) %>
+
+ <!-- inital grunt-contrib-jasmine scripts -->
+ <% generateScriptTags([].concat(scripts.polyfills, scripts.jasmine)); %>
+
+ <!-- Helpers - The less options -->
+ <% generateScriptTags(scripts.helpers); %>
+
+ <!-- Vendor - less.js and common code -->
+ <% generateScriptTags(scripts.vendor); %>
+
+ <!-- Spec -->
+ <% generateScriptTags(scripts.specs); %>
+
+ <!-- final grunt-contrib-jasmine scripts -->
+ <% generateScriptTags([].concat(scripts.reporters, scripts.start)); %>
+ </head>
+
+ <body>
+ <!-- content -->
+ </body>
+</html>
--- /dev/null
+@charset "UTF-8";
--- /dev/null
+#yelow #short {
+ color: #fea;
+}
+#yelow #long {
+ color: #ffeeaa;
+}
+#yelow #rgba {
+ color: rgba(255, 238, 170, 0.1);
+}
+#yelow #argb {
+ color: #1affeeaa;
+}
+#blue #short {
+ color: #00f;
+}
+#blue #long {
+ color: #0000ff;
+}
+#blue #rgba {
+ color: rgba(0, 0, 255, 0.1);
+}
+#blue #argb {
+ color: #1a0000ff;
+}
+#alpha #hsla {
+ color: rgba(61, 45, 41, 0.6);
+}
+#overflow .a {
+ color: #000000;
+}
+#overflow .b {
+ color: #ffffff;
+}
+#overflow .c {
+ color: #ffffff;
+}
+#overflow .d {
+ color: #00ff00;
+}
+#grey {
+ color: #c8c8c8;
+}
+#333333 {
+ color: #333333;
+}
+#808080 {
+ color: #808080;
+}
+#00ff00 {
+ color: #00ff00;
+}
+.lightenblue {
+ color: #3333ff;
+}
+.darkenblue {
+ color: #0000cc;
+}
+.unknowncolors {
+ color: blue2;
+ border: 2px solid superred;
+}
+.transparent {
+ color: transparent;
+ background-color: rgba(0, 0, 0, 0);
+}
+#alpha #fromvar {
+ opacity: 0.7;
+}
+#alpha #short {
+ opacity: 1;
+}
+#alpha #long {
+ opacity: 1;
+}
+#alpha #rgba {
+ opacity: 0.2;
+}
+#alpha #hsl {
+ opacity: 1;
+}
--- /dev/null
+/******************\
+* *
+* Comment Header *
+* *
+\******************/
+/*
+
+ Comment
+
+*/
+/*
+ * Comment Test
+ *
+ * - cloudhead (http://cloudhead.net)
+ *
+ */
+/* Colors
+ * ------
+ * #EDF8FC (background blue)
+ * #166C89 (darkest blue)
+ *
+ * Text:
+ * #333 (standard text) // A comment within a comment!
+ * #1F9EC9 (standard link)
+ *
+ */
+/* @group Variables
+------------------- */
+#comments,
+.comments {
+ /**/
+ color: red;
+ /* A C-style comment */
+ /* A C-style comment */
+ background-color: orange;
+ font-size: 12px;
+ /* lost comment */
+ content: "content";
+ border: 1px solid black;
+ padding: 0;
+ margin: 2em;
+}
+/* commented out
+ #more-comments {
+ color: grey;
+ }
+*/
+.selector,
+.lots,
+.comments {
+ color: #808080, /* blue */ #ffa500;
+ -webkit-border-radius: 2px /* webkit only */;
+ -moz-border-radius: 8px /* moz only with operation */;
+}
+.test {
+ color: 1px;
+}
+#last {
+ color: #0000ff;
+}
+/* */
+/* { */
+/* */
+/* */
+/* */
+#div {
+ color: #A33;
+}
+/* } */
--- /dev/null
+#colours{color1:#fea;color2:#fea;color3:rgba(255,238,170,0.1);string:"#ffeeaa";/*! but not this type
+ Note preserved whitespace
+ */}dimensions{val:.1px;val:0;val:4cm;val:.2;val:5;angles-must-have-unit:0deg;durations-must-have-unit:0s;length-doesnt-have-unit:0;width:auto\9}@page{marks:none;@top-left-corner{vertical-align:top}@top-left{vertical-align:top}}
\ No newline at end of file
--- /dev/null
+.comma-delimited {
+ text-shadow: -1px -1px 1px #ff0000, 6px 5px 5px #ffff00;
+ -moz-box-shadow: 0pt 0pt 2px rgba(255, 255, 255, 0.4) inset, 0pt 4px 6px rgba(255, 255, 255, 0.4) inset;
+ -webkit-transform: rotate(-0.0000000001deg);
+}
+@font-face {
+ font-family: Headline;
+ unicode-range: U+??????, U+0???, U+0-7F, U+A5;
+}
+.other {
+ -moz-transform: translate(0, 11em) rotate(-90deg);
+ transform: rotateX(45deg);
+}
+.item[data-cra_zy-attr1b-ut3=bold] {
+ font-weight: bold;
+}
+p:not([class*="lead"]) {
+ color: black;
+}
+input[type="text"].class#id[attr=32]:not(1) {
+ color: white;
+}
+div#id.class[a=1][b=2].class:not(1) {
+ color: white;
+}
+ul.comma > li:not(:only-child)::after {
+ color: white;
+}
+ol.comma > li:nth-last-child(2)::after {
+ color: white;
+}
+li:nth-child(4n+1),
+li:nth-child(-5n),
+li:nth-child(-n+2) {
+ color: white;
+}
+a[href^="http://"] {
+ color: black;
+}
+a[href$="http://"] {
+ color: black;
+}
+form[data-disabled] {
+ color: black;
+}
+p::before {
+ color: black;
+}
+#issue322 {
+ -webkit-animation: anim2 7s infinite ease-in-out;
+}
+@-webkit-keyframes frames {
+ 0% {
+ border: 1px;
+ }
+ 5.5% {
+ border: 2px;
+ }
+ 100% {
+ border: 3px;
+ }
+}
+@keyframes fontbulger1 {
+ to {
+ font-size: 15px;
+ }
+ from,
+ to {
+ font-size: 12px;
+ }
+ 0%,
+ 100% {
+ font-size: 12px;
+ }
+}
+.units {
+ font: 1.2rem/2rem;
+ font: 8vw/9vw;
+ font: 10vh/12vh;
+ font: 12vm/15vm;
+ font: 12vmin/15vmin;
+ font: 1.2ch/1.5ch;
+}
+@supports ( box-shadow: 2px 2px 2px black ) or
+ ( -moz-box-shadow: 2px 2px 2px black ) {
+ .outline {
+ box-shadow: 2px 2px 2px black;
+ -moz-box-shadow: 2px 2px 2px black;
+ }
+}
+@-x-document url-prefix(""github.com"") {
+ h1 {
+ color: red;
+ }
+}
+@viewport {
+ font-size: 10px;
+}
+@namespace foo url(http://www.example.com);
+foo|h1 {
+ color: blue;
+}
+foo|* {
+ color: yellow;
+}
+|h1 {
+ color: red;
+}
+*|h1 {
+ color: green;
+}
+h1 {
+ color: green;
+}
+.upper-test {
+ UpperCaseProperties: allowed;
+}
+@host {
+ div {
+ display: block;
+ }
+}
+::distributed(input::placeholder) {
+ color: #b3b3b3;
+}
--- /dev/null
+.escape\|random\|char {
+ color: red;
+}
+.mixin\!tUp {
+ font-weight: bold;
+}
+.\34 04 {
+ background: red;
+}
+.\34 04 strong {
+ color: #ff00ff;
+ font-weight: bold;
+}
+.trailingTest\+ {
+ color: red;
+}
+/* This hideous test of hideousness checks for the selector "blockquote" with various permutations of hex escapes */
+\62\6c\6f \63 \6B \0071 \000075o\74 e {
+ color: silver;
+}
+[ng\:cloak],
+ng\:form {
+ display: none;
+}
--- /dev/null
+.light {
+ color: green;
+}
+.see-the {
+ color: orange;
+}
+.hide-the {
+ color: green;
+}
+.multiple-conditions-1 {
+ color: red;
+}
+.inheritance .test {
+ color: black;
+}
+.inheritance:hover {
+ color: pink;
+}
--- /dev/null
+@charset "utf-8";
+div {
+ color: black;
+}
+div {
+ width: 99%;
+}
+* {
+ min-width: 45em;
+}
+h1,
+h2 > a > p,
+h3 {
+ color: none;
+}
+div.class {
+ color: blue;
+}
+div#id {
+ color: green;
+}
+.class#id {
+ color: purple;
+}
+.one.two.three {
+ color: grey;
+}
+@media print {
+ * {
+ font-size: 3em;
+ }
+}
+@media screen {
+ * {
+ font-size: 10px;
+ }
+}
+@font-face {
+ font-family: 'Garamond Pro';
+}
+a:hover,
+a:link {
+ color: #999;
+}
+p,
+p:first-child {
+ text-transform: none;
+}
+q:lang(no) {
+ quotes: none;
+}
+p + h1 {
+ font-size: 2.2em;
+}
+#shorthands {
+ border: 1px solid #000;
+ font: 12px/16px Arial;
+ font: 100%/16px Arial;
+ margin: 1px 0;
+ padding: 0 auto;
+}
+#more-shorthands {
+ margin: 0;
+ padding: 1px 0 2px 0;
+ font: normal small / 20px 'Trebuchet MS', Verdana, sans-serif;
+ font: 0/0 a;
+ border-radius: 5px / 10px;
+}
+.misc {
+ -moz-border-radius: 2px;
+ display: -moz-inline-stack;
+ width: .1em;
+ background-color: #009998;
+ background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), to(#0000ff));
+ margin: ;
+ filter: alpha(opacity=100);
+ width: auto\9;
+}
+.misc .nested-multiple {
+ multiple-semi-colons: yes;
+}
+#important {
+ color: red !important;
+ width: 100%!important;
+ height: 20px ! important;
+}
+@font-face {
+ font-family: font-a;
+}
+@font-face {
+ font-family: font-b;
+}
+.æøå {
+ margin: 0;
+}
--- /dev/null
+@charset "UTF-8";
+/* line 3, {pathimport}test.less */
+@media -sass-debug-info{filename{font-family:file\:\/\/{pathimportesc}test\.less}line{font-family:\000033}}
+/* @charset "ISO-8859-1"; */
+/* line 23, {pathimport}test.less */
+@media -sass-debug-info{filename{font-family:file\:\/\/{pathimportesc}test\.less}line{font-family:\0000323}}
+.tst3 {
+ color: grey;
+}
+/* line 15, {path}linenumbers.less */
+@media -sass-debug-info{filename{font-family:file\:\/\/{pathesc}linenumbers\.less}line{font-family:\0000315}}
+.test1 {
+ color: black;
+}
+/* line 6, {path}linenumbers.less */
+@media -sass-debug-info{filename{font-family:file\:\/\/{pathesc}linenumbers\.less}line{font-family:\000036}}
+.test2 {
+ color: red;
+}
+@media all {
+ /* line 5, {pathimport}test.less */
+ @media -sass-debug-info{filename{font-family:file\:\/\/{pathimportesc}test\.less}line{font-family:\000035}}
+ .tst {
+ color: black;
+ }
+}
+@media all and screen {
+ /* line 7, {pathimport}test.less */
+ @media -sass-debug-info{filename{font-family:file\:\/\/{pathimportesc}test\.less}line{font-family:\000037}}
+ .tst {
+ color: red;
+ }
+ /* line 9, {pathimport}test.less */
+ @media -sass-debug-info{filename{font-family:file\:\/\/{pathimportesc}test\.less}line{font-family:\000039}}
+ .tst .tst3 {
+ color: white;
+ }
+}
+/* line 18, {pathimport}test.less */
+@media -sass-debug-info{filename{font-family:file\:\/\/{pathimportesc}test\.less}line{font-family:\0000318}}
+.tst2 {
+ color: white;
+}
--- /dev/null
+@charset "UTF-8";
+/* line 3, {pathimport}test.less */
+/* @charset "ISO-8859-1"; */
+/* line 23, {pathimport}test.less */
+.tst3 {
+ color: grey;
+}
+/* line 15, {path}linenumbers.less */
+.test1 {
+ color: black;
+}
+/* line 6, {path}linenumbers.less */
+.test2 {
+ color: red;
+}
+@media all {
+ /* line 5, {pathimport}test.less */
+ .tst {
+ color: black;
+ }
+}
+@media all and screen {
+ /* line 7, {pathimport}test.less */
+ .tst {
+ color: red;
+ }
+ /* line 9, {pathimport}test.less */
+ .tst .tst3 {
+ color: white;
+ }
+}
+/* line 18, {pathimport}test.less */
+.tst2 {
+ color: white;
+}
--- /dev/null
+@charset "UTF-8";
+@media -sass-debug-info{filename{font-family:file\:\/\/{pathimportesc}test\.less}line{font-family:\000033}}
+/* @charset "ISO-8859-1"; */
+@media -sass-debug-info{filename{font-family:file\:\/\/{pathimportesc}test\.less}line{font-family:\0000323}}
+.tst3 {
+ color: grey;
+}
+@media -sass-debug-info{filename{font-family:file\:\/\/{pathesc}linenumbers\.less}line{font-family:\0000315}}
+.test1 {
+ color: black;
+}
+@media -sass-debug-info{filename{font-family:file\:\/\/{pathesc}linenumbers\.less}line{font-family:\000036}}
+.test2 {
+ color: red;
+}
+@media all {
+ @media -sass-debug-info{filename{font-family:file\:\/\/{pathimportesc}test\.less}line{font-family:\000035}}
+ .tst {
+ color: black;
+ }
+}
+@media all and screen {
+ @media -sass-debug-info{filename{font-family:file\:\/\/{pathimportesc}test\.less}line{font-family:\000037}}
+ .tst {
+ color: red;
+ }
+ @media -sass-debug-info{filename{font-family:file\:\/\/{pathimportesc}test\.less}line{font-family:\000039}}
+ .tst .tst3 {
+ color: white;
+ }
+}
+@media -sass-debug-info{filename{font-family:file\:\/\/{pathimportesc}test\.less}line{font-family:\0000318}}
+.tst2 {
+ color: white;
+}
--- /dev/null
+.a,
+.b,
+.c {
+ color: black;
+}
+.f,
+.e,
+.d {
+ color: black;
+}
+.g.h,
+.i.j.h,
+.k.j.h {
+ color: black;
+}
+.i.j,
+.k.j {
+ color: white;
+}
+.l,
+.m,
+.n,
+.o,
+.p,
+.q,
+.r,
+.s,
+.t {
+ color: black;
+}
+.u,
+.v.u.v {
+ color: black;
+}
+.w,
+.v.w.v {
+ color: black;
+}
+.x,
+.y,
+.z {
+ color: x;
+}
+.y,
+.z,
+.x {
+ color: y;
+}
+.z,
+.x,
+.y {
+ color: z;
+}
+.va,
+.vb,
+.vc {
+ color: black;
+}
+.vb,
+.vc {
+ color: white;
+}
+@media tv {
+ .ma,
+ .mb,
+ .mc {
+ color: black;
+ }
+ .md,
+ .ma,
+ .mb,
+ .mc {
+ color: white;
+ }
+}
+@media tv and plasma {
+ .me,
+ .mf {
+ background: red;
+ }
+}
--- /dev/null
+.clearfix,
+.foo,
+.bar {
+ *zoom: 1;
+}
+.clearfix:after,
+.foo:after,
+.bar:after {
+ content: '';
+ display: block;
+ clear: both;
+ height: 0;
+}
+.foo {
+ color: red;
+}
+.bar {
+ color: blue;
+}
--- /dev/null
+.replace.replace .replace,
+.c.replace + .replace .replace,
+.replace.replace .c,
+.c.replace + .replace .c,
+.rep_ace {
+ prop: copy-paste-replace;
+}
+.a .b .c {
+ prop: not_effected;
+}
+.a,
+.effected {
+ prop: is_effected;
+}
+.a .b {
+ prop: not_effected;
+}
+.a .b.c {
+ prop: not_effected;
+}
+.c .b .a,
+.a .b .a,
+.c .a .a,
+.a .a .a,
+.c .b .c,
+.a .b .c,
+.c .a .c,
+.a .a .c {
+ prop: not_effected;
+}
+.e.e,
+.dbl {
+ prop: extend-double;
+}
+.e.e:hover {
+ hover: not-extended;
+}
--- /dev/null
+.ext1 .ext2,
+.all .ext2 {
+ background: black;
+}
+@media tv {
+ .ext1 .ext3,
+ .tv-lowres .ext3,
+ .all .ext3 {
+ color: white;
+ }
+ .tv-lowres {
+ background: blue;
+ }
+}
+@media tv and hires {
+ .ext1 .ext4,
+ .tv-hires .ext4,
+ .all .ext4 {
+ color: green;
+ }
+ .tv-hires {
+ background: red;
+ }
+}
--- /dev/null
+.sidebar,
+.sidebar2,
+.type1 .sidebar3,
+.type2.sidebar4 {
+ width: 300px;
+ background: red;
+}
+.sidebar .box,
+.sidebar2 .box,
+.type1 .sidebar3 .box,
+.type2.sidebar4 .box {
+ background: #FFF;
+ border: 1px solid #000;
+ margin: 10px 0;
+}
+.sidebar2 {
+ background: blue;
+}
+.type1 .sidebar3 {
+ background: green;
+}
+.type2.sidebar4 {
+ background: red;
+}
+.button,
+.submit {
+ color: black;
+}
+.button:hover,
+.submit:hover {
+ color: white;
+}
+.button2 :hover {
+ nested: white;
+}
+.button2 :hover {
+ notnested: black;
+}
+.amp-test-h,
+.amp-test-f.amp-test-c .amp-test-a.amp-test-d.amp-test-a.amp-test-e + .amp-test-c .amp-test-a.amp-test-d.amp-test-a.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-a.amp-test-d.amp-test-a.amp-test-e + .amp-test-c .amp-test-a.amp-test-d.amp-test-b.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-a.amp-test-d.amp-test-a.amp-test-e + .amp-test-c .amp-test-b.amp-test-d.amp-test-a.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-a.amp-test-d.amp-test-a.amp-test-e + .amp-test-c .amp-test-b.amp-test-d.amp-test-b.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-a.amp-test-d.amp-test-b.amp-test-e + .amp-test-c .amp-test-a.amp-test-d.amp-test-a.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-a.amp-test-d.amp-test-b.amp-test-e + .amp-test-c .amp-test-a.amp-test-d.amp-test-b.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-a.amp-test-d.amp-test-b.amp-test-e + .amp-test-c .amp-test-b.amp-test-d.amp-test-a.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-a.amp-test-d.amp-test-b.amp-test-e + .amp-test-c .amp-test-b.amp-test-d.amp-test-b.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-b.amp-test-d.amp-test-a.amp-test-e + .amp-test-c .amp-test-a.amp-test-d.amp-test-a.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-b.amp-test-d.amp-test-a.amp-test-e + .amp-test-c .amp-test-a.amp-test-d.amp-test-b.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-b.amp-test-d.amp-test-a.amp-test-e + .amp-test-c .amp-test-b.amp-test-d.amp-test-a.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-b.amp-test-d.amp-test-a.amp-test-e + .amp-test-c .amp-test-b.amp-test-d.amp-test-b.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-b.amp-test-d.amp-test-b.amp-test-e + .amp-test-c .amp-test-a.amp-test-d.amp-test-a.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-b.amp-test-d.amp-test-b.amp-test-e + .amp-test-c .amp-test-a.amp-test-d.amp-test-b.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-b.amp-test-d.amp-test-b.amp-test-e + .amp-test-c .amp-test-b.amp-test-d.amp-test-a.amp-test-e.amp-test-g,
+.amp-test-f.amp-test-c .amp-test-b.amp-test-d.amp-test-b.amp-test-e + .amp-test-c .amp-test-b.amp-test-d.amp-test-b.amp-test-e.amp-test-g {
+ test: extended by masses of selectors;
+}
--- /dev/null
+.error,
+.badError {
+ border: 1px #f00;
+ background: #fdd;
+}
+.error.intrusion,
+.badError.intrusion {
+ font-size: 1.3em;
+ font-weight: bold;
+}
+.intrusion .error,
+.intrusion .badError {
+ display: none;
+}
+.badError {
+ border-width: 3px;
+}
+.foo .bar,
+.foo .baz,
+.ext1 .ext2 .bar,
+.ext1 .ext2 .baz,
+.ext3 .bar,
+.ext3 .baz,
+.ext4 .bar,
+.ext4 .baz {
+ display: none;
+}
+div.ext5,
+.ext6 > .ext5,
+div.ext7,
+.ext6 > .ext7 {
+ width: 100px;
+}
+.ext,
+.a .c,
+.b .c {
+ test: 1;
+}
+.a,
+.b {
+ test: 2;
+}
+.a .c,
+.b .c {
+ test: 3;
+}
+.a .c .d,
+.b .c .d {
+ test: 4;
+}
+.replace.replace .replace,
+.c.replace + .replace .replace,
+.replace.replace .c,
+.c.replace + .replace .c,
+.rep_ace.rep_ace .rep_ace,
+.c.rep_ace + .rep_ace .rep_ace,
+.rep_ace.rep_ace .c,
+.c.rep_ace + .rep_ace .c {
+ prop: copy-paste-replace;
+}
+.attributes [data="test"],
+.attributes .attributes .attribute-test {
+ extend: attributes;
+}
+.attributes [data],
+.attributes .attributes .attribute-test2 {
+ extend: attributes2;
+}
+.attributes [data="test3"],
+.attributes .attributes .attribute-test {
+ extend: attributes2;
+}
+.header .header-nav,
+.footer .footer-nav {
+ background: red;
+}
+.header .header-nav:before,
+.footer .footer-nav:before {
+ background: blue;
+}
--- /dev/null
+.error,
+.badError {
+ border: 1px #f00;
+ background: #fdd;
+}
+.error.intrusion,
+.badError.intrusion {
+ font-size: 1.3em;
+ font-weight: bold;
+}
+.intrusion .error,
+.intrusion .badError {
+ display: none;
+}
+.badError {
+ border-width: 3px;
+}
+.foo .bar,
+.foo .baz,
+.ext1 .ext2 .bar,
+.ext1 .ext2 .baz,
+.ext3 .bar,
+.ext3 .baz,
+.foo .ext3,
+.ext4 .bar,
+.ext4 .baz,
+.foo .ext4 {
+ display: none;
+}
+div.ext5,
+.ext6 > .ext5,
+div.ext7,
+.ext6 > .ext7 {
+ width: 100px;
+}
+.ext8.ext9,
+.fuu {
+ result: add-foo;
+}
+.ext8 .ext9,
+.ext8 + .ext9,
+.ext8 > .ext9,
+.buu,
+.zap,
+.zoo {
+ result: bar-matched;
+}
+.ext8.nomatch {
+ result: none;
+}
+.ext8 .ext9,
+.buu {
+ result: match-nested-bar;
+}
+.ext8.ext9,
+.fuu {
+ result: match-nested-foo;
+}
+.aa,
+.cc {
+ color: black;
+}
+.aa .dd,
+.aa .ee {
+ background: red;
+}
+.bb,
+.cc,
+.ee,
+.ff {
+ background: red;
+}
+.bb .bb,
+.ff .ff {
+ color: black;
+}
--- /dev/null
+.multiunit {
+ length: 6;
+ extract: abc "abc" 1 1px 1% #112233;
+}
+.incorrect-index {
+ v1: extract(a b c, 5);
+ v2: extract(a, b, c, -2);
+}
+.scalar {
+ var-value: variable;
+ var-length: 1;
+ ill-index: extract(variable, 2);
+ name-value: name;
+ string-value: "string";
+ number-value: 12345678;
+ color-value: #0000ff;
+ rgba-value: rgba(80, 160, 240, 0.67);
+ empty-value: ;
+ name-length: 1;
+ string-length: 1;
+ number-length: 1;
+ color-length: 1;
+ rgba-length: 1;
+ empty-length: 1;
+}
+.mixin-arguments-1 {
+ length: 4;
+ extract: c | b | a;
+}
+.mixin-arguments-2 {
+ length: 4;
+ extract: c | b | a;
+}
+.mixin-arguments-3 {
+ length: 4;
+ extract: c | b | a;
+}
+.mixin-arguments-4 {
+ length: 0;
+ extract: extract(, 2) | extract(, 1);
+}
+.mixin-arguments-2 {
+ length: 4;
+ extract: c | b | a;
+}
+.mixin-arguments-3 {
+ length: 4;
+ extract: c | b | a;
+}
+.mixin-arguments-4 {
+ length: 3;
+ extract: c | b;
+}
+.mixin-arguments-2 {
+ length: 4;
+ extract: 3 | 2 | 1;
+}
+.mixin-arguments-3 {
+ length: 4;
+ extract: 3 | 2 | 1;
+}
+.mixin-arguments-4 {
+ length: 3;
+ extract: 3 | 2;
+}
+.md-space-comma {
+ length-1: 3;
+ extract-1: 1 2 3;
+ length-2: 3;
+ extract-2: 2;
+}
+.md-space-comma-as-args-2 {
+ length: 3;
+ extract: "x" "y" "z" | 1 2 3 | a b c;
+}
+.md-space-comma-as-args-3 {
+ length: 3;
+ extract: "x" "y" "z" | 1 2 3 | a b c;
+}
+.md-space-comma-as-args-4 {
+ length: 2;
+ extract: "x" "y" "z" | 1 2 3;
+}
+.md-cat-space-comma {
+ length-1: 3;
+ extract-1: 1 2 3;
+ length-2: 3;
+ extract-2: 2;
+}
+.md-cat-space-comma-as-args-2 {
+ length: 3;
+ extract: "x" "y" "z" | 1 2 3 | a b c;
+}
+.md-cat-space-comma-as-args-3 {
+ length: 3;
+ extract: "x" "y" "z" | 1 2 3 | a b c;
+}
+.md-cat-space-comma-as-args-4 {
+ length: 2;
+ extract: "x" "y" "z" | 1 2 3;
+}
+.md-cat-comma-space {
+ length-1: 3;
+ extract-1: 1, 2, 3;
+ length-2: 3;
+ extract-2: 2;
+}
+.md-cat-comma-space-as-args-1 {
+ length: 3;
+ extract: "x", "y", "z" | 1, 2, 3 | a, b, c;
+}
+.md-cat-comma-space-as-args-2 {
+ length: 3;
+ extract: "x", "y", "z" | 1, 2, 3 | a, b, c;
+}
+.md-cat-comma-space-as-args-3 {
+ length: 3;
+ extract: "x", "y", "z" | 1, 2, 3 | a, b, c;
+}
+.md-cat-comma-space-as-args-4 {
+ length: 0;
+ extract: extract(, 2) | extract(, 1);
+}
+.md-3D {
+ length-1: 2;
+ extract-1: a b c d, 1 2 3 4;
+ length-2: 2;
+ extract-2: 5 6 7 8;
+ length-3: 4;
+ extract-3: 7;
+ length-4: 1;
+ extract-4: 8;
+}
--- /dev/null
+#functions {
+ color: #660000;
+ width: 16;
+ height: undefined("self");
+ border-width: 5;
+ variable: 11;
+ background: linear-gradient(#000000, #ffffff);
+}
+#built-in {
+ escaped: -Some::weird(#thing, y);
+ lighten: #ffcccc;
+ darken: #330000;
+ saturate: #203c31;
+ desaturate: #29332f;
+ greyscale: #2e2e2e;
+ hsl-clamp: #ffffff;
+ spin-p: #bf6a40;
+ spin-n: #bf4055;
+ luma-white: 100%;
+ luma-black: 0%;
+ luma-black-alpha: 0%;
+ luma-red: 21%;
+ luma-green: 72%;
+ luma-blue: 7%;
+ luma-yellow: 93%;
+ luma-cyan: 79%;
+ luma-white-alpha: 50%;
+ contrast-filter: contrast(30%);
+ saturate-filter: saturate(5%);
+ contrast-white: #000000;
+ contrast-black: #ffffff;
+ contrast-red: #ffffff;
+ contrast-green: #000000;
+ contrast-blue: #ffffff;
+ contrast-yellow: #000000;
+ contrast-cyan: #000000;
+ contrast-light: #111111;
+ contrast-dark: #eeeeee;
+ contrast-wrongorder: #111111;
+ contrast-light-thresh: #111111;
+ contrast-dark-thresh: #eeeeee;
+ contrast-high-thresh: #eeeeee;
+ contrast-low-thresh: #111111;
+ contrast-light-thresh-per: #111111;
+ contrast-dark-thresh-per: #eeeeee;
+ contrast-high-thresh-per: #eeeeee;
+ contrast-low-thresh-per: #111111;
+ format: "rgb(32, 128, 64)";
+ format-string: "hello world";
+ format-multiple: "hello earth 2";
+ format-url-encode: "red is %23ff0000";
+ eformat: rgb(32, 128, 64);
+ unitless: 12;
+ unit: 14em;
+ hue: 98;
+ saturation: 12%;
+ lightness: 95%;
+ hsvhue: 98;
+ hsvsaturation: 12%;
+ hsvvalue: 95%;
+ red: 255;
+ green: 255;
+ blue: 255;
+ rounded: 11;
+ rounded-two: 10.67;
+ roundedpx: 3px;
+ roundedpx-three: 3.333px;
+ rounded-percentage: 10%;
+ ceil: 11px;
+ floor: 12px;
+ sqrt: 5px;
+ pi: 3.141592653589793;
+ mod: 2m;
+ abs: 4%;
+ tan: 0.9004040442978399;
+ sin: 0.17364817766693033;
+ cos: 0.8438539587324921;
+ atan: 0.1rad;
+ atan: 34.00000000000001deg;
+ atan: 45.00000000000001deg;
+ pow: 64px;
+ pow: 64;
+ pow: 27;
+ min: 0;
+ min: min("junk", 5);
+ min: 3pt;
+ max: 3;
+ max: max(8%, 1cm);
+ percentage: 20%;
+ color: #ff0011;
+ tint: #898989;
+ tint-full: #ffffff;
+ tint-percent: #898989;
+ shade: #686868;
+ shade-full: #000000;
+ shade-percent: #686868;
+ fade-out: rgba(255, 0, 0, 0.95);
+ fade-in: rgba(255, 0, 0, 0.9500000000000001);
+ hsv: #4d2926;
+ hsva: rgba(77, 40, 38, 0.2);
+ mix: #ff3300;
+ mix-0: #ffff00;
+ mix-100: #ff0000;
+ mix-weightless: #ff8000;
+ mixt: rgba(255, 0, 0, 0.5);
+}
+#built-in .is-a {
+ color: true;
+ color1: true;
+ color2: true;
+ color3: true;
+ keyword: true;
+ number: true;
+ string: true;
+ pixel: true;
+ percent: true;
+ em: true;
+ cat: true;
+}
+#alpha {
+ alpha: rgba(153, 94, 51, 0.6);
+ alpha2: 0.5;
+ alpha3: 0;
+}
+#blendmodes {
+ multiply: #ed0000;
+ screen: #f600f6;
+ overlay: #ed0000;
+ softlight: #ff0000;
+ hardlight: #0000ed;
+ difference: #f600f6;
+ exclusion: #f600f6;
+ average: #7b007b;
+ negation: #d73131;
+}
+#extract-and-length {
+ extract: 3 2 1 C B A;
+ length: 6;
+}
--- /dev/null
+.nav {
+ filter: progid:DXImageTransform.Microsoft.Alpha(opacity=20);
+ filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#333333", endColorstr="#000000", GradientType=0);
+}
+.evalTest1 {
+ filter: progid:DXImageTransform.Microsoft.Alpha(opacity=30);
+ filter: progid:DXImageTransform.Microsoft.Alpha(opacity=5);
+}
--- /dev/null
+this isn't very valid CSS.
+@media (min-width: 600px) {
+ #css { color: yellow; }
+
+}
--- /dev/null
+body {
+ width: 100%;
+}
+.a {
+ var: test;
+}
--- /dev/null
+#import {
+ color: #ff0000;
+}
+body {
+ width: 100%;
+}
+.test-f {
+ height: 10px;
+}
+body {
+ width: 100%;
+}
+.test-f {
+ height: 10px;
+}
--- /dev/null
+/*
+ The media statement above is invalid (no selector)
+ We should ban invalid media queries with properties and no selector?
+*/
+.visible {
+ color: red;
+}
+.visible .c {
+ color: green;
+}
+.visible {
+ color: green;
+}
+.visible:hover {
+ color: green;
+}
+.visible {
+ color: green;
+}
+.only-with-visible + .visible,
+.visible + .only-with-visible,
+.visible + .visible {
+ color: green;
+}
+.only-with-visible + .visible .sub,
+.visible + .only-with-visible .sub,
+.visible + .visible .sub {
+ color: green;
+}
+.b {
+ color: red;
+ color: green;
+}
+.b .c {
+ color: green;
+}
+.b:hover {
+ color: green;
+}
+.b {
+ color: green;
+}
+.b + .b {
+ color: green;
+}
+.b + .b .sub {
+ color: green;
+}
+.y {
+ pulled-in: yes;
+}
+/* comment pulled in */
+.visible {
+ extend: test;
+}
--- /dev/null
+@import url(http://fonts.googleapis.com/css?family=Open+Sans);
+@import url(/absolute/something.css) screen and (color) and (max-width: 600px);
+@import url("//ha.com/file.css") (min-width: 100px);
+#import-test {
+ height: 10px;
+ color: #ff0000;
+ width: 10px;
+ height: 30%;
+}
+@media screen and (max-width: 600px) {
+ body {
+ width: 100%;
+ }
+}
+#import {
+ color: #ff0000;
+}
+.mixin {
+ height: 10px;
+ color: #ff0000;
+}
+@media screen and (max-width: 601px) {
+ #css {
+ color: yellow;
+ }
+}
+@media screen and (max-width: 602px) {
+ body {
+ width: 100%;
+ }
+}
+@media screen and (max-width: 603px) {
+ #css {
+ color: yellow;
+ }
+}
--- /dev/null
+.eval {
+ js: 42;
+ js: 2;
+ js: "hello world";
+ js: 1, 2, 3;
+ title: "string";
+ ternary: true;
+ multiline: 2;
+}
+.scope {
+ var: 42;
+ escaped: 7px;
+}
+.vars {
+ width: 8;
+}
+.escape-interpol {
+ width: hello world;
+}
+.arrays {
+ ary: "1, 2, 3";
+ ary1: "1, 2, 3";
+}
--- /dev/null
+.lazy-eval {
+ width: 100%;
+}
--- /dev/null
+@media (-o-min-device-pixel-ratio: 2/1) {
+ .test-math-and-units {
+ font: ignores 0/0 rules;
+ test-division: 7em;
+ simple: 2px;
+ }
+}
--- /dev/null
+@media print {
+ .class {
+ color: blue;
+ }
+ .class .sub {
+ width: 42;
+ }
+ .top,
+ header > h1 {
+ color: #444444;
+ }
+}
+@media screen {
+ body {
+ max-width: 480;
+ }
+}
+@media all and (device-aspect-ratio: 16 / 9) {
+ body {
+ max-width: 800px;
+ }
+}
+@media all and (orientation: portrait) {
+ aside {
+ float: none;
+ }
+}
+@media handheld and (min-width: 42), screen and (min-width: 20em) {
+ body {
+ max-width: 480px;
+ }
+}
+@media print {
+ body {
+ padding: 20px;
+ }
+ body header {
+ background-color: red;
+ }
+}
+@media print and (orientation: landscape) {
+ body {
+ margin-left: 20px;
+ }
+}
+@media screen {
+ .sidebar {
+ width: 300px;
+ }
+}
+@media screen and (orientation: landscape) {
+ .sidebar {
+ width: 500px;
+ }
+}
+@media a and b {
+ .first .second .third {
+ width: 300px;
+ }
+ .first .second .fourth {
+ width: 3;
+ }
+}
+@media a and b and c {
+ .first .second .third {
+ width: 500px;
+ }
+}
+@media a, b and c {
+ body {
+ width: 95%;
+ }
+}
+@media a and x, b and c and x, a and y, b and c and y {
+ body {
+ width: 100%;
+ }
+}
+.a {
+ background: black;
+}
+@media handheld {
+ .a {
+ background: white;
+ }
+}
+@media handheld and (max-width: 100px) {
+ .a {
+ background: red;
+ }
+}
+.b {
+ background: black;
+}
+@media handheld {
+ .b {
+ background: white;
+ }
+}
+@media handheld and (max-width: 200px) {
+ .b {
+ background: red;
+ }
+}
+@media only screen and (max-width: 200px) {
+ body {
+ width: 480px;
+ }
+}
+@media print {
+ @page :left {
+ margin: 0.5cm;
+ }
+ @page :right {
+ margin: 0.5cm;
+ }
+ @page Test:first {
+ margin: 1cm;
+ }
+ @page :first {
+ size: 8.5in 11in;
+
+ @top-left {
+ margin: 1cm;
+ }
+ @top-left-corner {
+ margin: 1cm;
+ }
+ @top-center {
+ margin: 1cm;
+ }
+ @top-right {
+ margin: 1cm;
+ }
+ @top-right-corner {
+ margin: 1cm;
+ }
+ @bottom-left {
+ margin: 1cm;
+ }
+ @bottom-left-corner {
+ margin: 1cm;
+ }
+ @bottom-center {
+ margin: 1cm;
+ }
+ @bottom-right {
+ margin: 1cm;
+ }
+ @bottom-right-corner {
+ margin: 1cm;
+ }
+ @left-top {
+ margin: 1cm;
+ }
+ @left-middle {
+ margin: 1cm;
+ }
+ @left-bottom {
+ margin: 1cm;
+ }
+ @right-top {
+ margin: 1cm;
+ }
+ @right-middle {
+ content: "Page " counter(page);
+ }
+ @right-bottom {
+ margin: 1cm;
+ }
+ }
+}
+@media (-webkit-min-device-pixel-ratio: 2), (min--moz-device-pixel-ratio: 2), (-o-min-device-pixel-ratio: 2/1), (min-resolution: 2dppx), (min-resolution: 128dpcm) {
+ .b {
+ background: red;
+ }
+}
+body {
+ background: red;
+}
+@media (max-width: 500px) {
+ body {
+ background: green;
+ }
+}
+@media (max-width: 1000px) {
+ body {
+ background: red;
+ background: blue;
+ }
+}
+@media (max-width: 1000px) and (max-width: 500px) {
+ body {
+ background: green;
+ }
+}
+@media (max-width: 1200px) {
+ /* a comment */
+}
+@media (max-width: 1200px) and (max-width: 900px) {
+ body {
+ font-size: 11px;
+ }
+}
+@media (min-width: 480px) {
+ .nav-justified > li {
+ display: table-cell;
+ }
+}
+@media (min-width: 768px) and (min-width: 480px) {
+ .menu > li {
+ display: table-cell;
+ }
+}
+@media all and tv {
+ .all-and-tv-variables {
+ var: all-and-tv;
+ }
+}
--- /dev/null
+.test1 {
+ transform: rotate(90deg), skew(30deg), scale(2, 4);
+}
+.test2 {
+ transform: rotate(90deg), skew(30deg);
+ transform: scaleX(45deg);
+}
+.test3 {
+ transform: scaleX(45deg);
+ background: url(data://img1.png);
+}
+.test4 {
+ transform: rotate(90deg), skew(30deg);
+ transform: scale(2, 4) !important;
+}
+.test5 {
+ transform: rotate(90deg), skew(30deg);
+ transform: scale(2, 4) !important;
+}
+.test6 {
+ transform: scale(2, 4);
+}
--- /dev/null
+#hidden {
+ color: transparent;
+}
+#hidden1 {
+ color: transparent;
+}
+.two-args {
+ color: blue;
+ width: 10px;
+ height: 99%;
+ border: 2px dotted #000000;
+}
+.one-arg {
+ width: 15px;
+ height: 49%;
+}
+.no-parens {
+ width: 5px;
+ height: 49%;
+}
+.no-args {
+ width: 5px;
+ height: 49%;
+}
+.var-args {
+ width: 45;
+ height: 17%;
+}
+.multi-mix {
+ width: 10px;
+ height: 29%;
+ margin: 4;
+ padding: 5;
+}
+body {
+ padding: 30px;
+ color: #ff0000;
+}
+.scope-mix {
+ width: 8;
+}
+.content {
+ width: 600px;
+}
+.content .column {
+ margin: 600px;
+}
+#same-var-name {
+ radius: 5px;
+}
+#var-inside {
+ width: 10px;
+}
+.arguments {
+ border: 1px solid #000000;
+ width: 1px;
+}
+.arguments2 {
+ border: 0px;
+ width: 0px;
+}
+.arguments3 {
+ border: 0px;
+ width: 0px;
+}
+.arguments4 {
+ border: 0 1 2 3 4;
+ rest: 1 2 3 4;
+ width: 0;
+}
+.edge-case {
+ border: "{";
+ width: "{";
+}
+.slash-vs-math {
+ border-radius: 2px/5px;
+ border-radius: 5px/10px;
+ border-radius: 6px;
+}
+.comma-vs-semi-colon {
+ one: a;
+ two: b, c;
+ one: d, e;
+ two: f;
+ one: g;
+ one: h;
+ one: i;
+ one: j;
+ one: k;
+ two: l;
+ one: m, n;
+ one: o, p;
+ two: q;
+ one: r, s;
+ two: t;
+}
+#named-conflict {
+ four: a, 11, 12, 13;
+ four: a, 21, 22, 23;
+}
+.test-mixin-default-arg {
+ defaults: 1px 1px 1px;
+ defaults: 2px 2px 2px;
+}
+.selector {
+ margin: 2, 2, 2, 2;
+}
+.selector2 {
+ margin: 2, 2, 2, 2;
+}
+.selector3 {
+ margin: 4;
+}
--- /dev/null
+.class {
+ width: 99px;
+}
+.overwrite {
+ width: 99px;
+}
+.nested .class {
+ width: 5px;
+}
--- /dev/null
+.light1 {
+ color: white;
+ margin: 1px;
+}
+.light2 {
+ color: black;
+ margin: 1px;
+}
+.max1 {
+ width: 6;
+}
+.max2 {
+ width: 8;
+}
+.glob1 {
+ margin: auto auto;
+}
+.ops1 {
+ height: gt-or-eq;
+ height: lt-or-eq;
+ height: lt-or-eq-alias;
+}
+.ops2 {
+ height: gt-or-eq;
+ height: not-eq;
+}
+.ops3 {
+ height: lt-or-eq;
+ height: lt-or-eq-alias;
+ height: not-eq;
+}
+.default1 {
+ content: default;
+}
+.test1 {
+ content: "true.";
+}
+.test2 {
+ content: "false.";
+}
+.test3 {
+ content: "false.";
+}
+.test4 {
+ content: "false.";
+}
+.test5 {
+ content: "false.";
+}
+.bool1 {
+ content: true and true;
+ content: true;
+ content: false, true;
+ content: false and true and true, true;
+ content: false, true and true;
+ content: false, false, true;
+ content: false, true and true and true, false;
+ content: not false;
+ content: not false and false, not false;
+}
+.equality-units {
+ test: pass;
+}
+.colorguardtest {
+ content: is #ff0000;
+ content: is not #0000ff its #ff0000;
+ content: is not #0000ff its #800080;
+}
+.stringguardtest {
+ content: is theme1;
+ content: is not theme2;
+ content: is theme1 no quotes;
+}
+#tryNumberPx {
+ catch: all;
+ declare: 4;
+ declare: 4px;
+}
+.call-lock-mixin .call-inner-lock-mixin {
+ a: 1;
+ x: 1;
+}
--- /dev/null
+.class {
+ border: 1;
+ boxer: 1;
+ border-width: 1;
+ border: 2 !important;
+ boxer: 2 !important;
+ border-width: 2 !important;
+ border: 3;
+ boxer: 3;
+ border-width: 3;
+ border: 4 !important;
+ boxer: 4 !important;
+ border-width: 4 !important;
+ border: 5;
+ boxer: 5;
+ border-width: 5;
+ border: 0 !important;
+ boxer: 0 !important;
+ border-width: 0 !important;
+ border: 9 !important;
+ border: 9;
+ boxer: 9;
+ border-width: 9;
+}
+.class .inner {
+ test: 1;
+}
+.class .inner {
+ test: 2 !important;
+}
+.class .inner {
+ test: 3;
+}
+.class .inner {
+ test: 4 !important;
+}
+.class .inner {
+ test: 5;
+}
+.class .inner {
+ test: 0 !important;
+}
+.class .inner {
+ test: 9;
+}
--- /dev/null
+.named-arg {
+ color: blue;
+ width: 5px;
+ height: 99%;
+ args: 1px 100%;
+ text-align: center;
+}
+.class {
+ width: 5px;
+ height: 19%;
+ args: 1px 20%;
+}
+.all-args-wrong-args {
+ width: 10px;
+ height: 9%;
+ args: 2px 10%;
+}
+.named-args2 {
+ width: 15px;
+ height: 49%;
+ color: #646464;
+}
+.named-args3 {
+ width: 5px;
+ height: 29%;
+ color: #123456;
+}
--- /dev/null
+.class .inner {
+ height: 300;
+}
+.class .inner .innest {
+ width: 30;
+ border-width: 60;
+}
+.class2 .inner {
+ height: 600;
+}
+.class2 .inner .innest {
+ width: 60;
+ border-width: 120;
+}
--- /dev/null
+.zero {
+ variadic: true;
+ zero: 0;
+ one: 1;
+ two: 2;
+ three: 3;
+}
+.one {
+ variadic: true;
+ one: 1;
+ one-req: 1;
+ two: 2;
+ three: 3;
+}
+.two {
+ variadic: true;
+ two: 2;
+ three: 3;
+}
+.three {
+ variadic: true;
+ three-req: 3;
+ three: 3;
+}
+.left {
+ left: 1;
+}
+.right {
+ right: 1;
+}
+.border-right {
+ color: black;
+ border-right: 4px;
+}
+.border-left {
+ color: black;
+ border-left: 4px;
+}
+.only-right {
+ right: 33;
+}
+.only-left {
+ left: 33;
+}
+.left-right {
+ both: 330;
+}
--- /dev/null
+.mixin {
+ border: 1px solid black;
+}
+.mixout {
+ border-color: orange;
+}
+.borders {
+ border-style: dashed;
+}
+#namespace .borders {
+ border-style: dotted;
+}
+#namespace .biohazard {
+ content: "death";
+}
+#namespace .biohazard .man {
+ color: transparent;
+}
+#theme > .mixin {
+ background-color: grey;
+}
+#container {
+ color: black;
+ border: 1px solid black;
+ border-color: orange;
+ background-color: grey;
+}
+#header .milk {
+ color: white;
+ border: 1px solid black;
+ background-color: grey;
+}
+#header #cookie {
+ border-style: dashed;
+}
+#header #cookie .chips {
+ border-style: dotted;
+}
+#header #cookie .chips .calories {
+ color: black;
+ border: 1px solid black;
+ border-color: orange;
+ background-color: grey;
+}
+.secure-zone {
+ color: transparent;
+}
+.direct {
+ border-style: dotted;
+}
+.bo,
+.bar {
+ width: 100%;
+}
+.bo {
+ border: 1px;
+}
+.ar.bo.ca {
+ color: black;
+}
+.jo.ki {
+ background: none;
+}
+.amp.support {
+ color: orange;
+}
+.amp.support .higher {
+ top: 0px;
+}
+.amp.support.deeper {
+ height: auto;
+}
+.extended {
+ width: 100%;
+ border: 1px;
+ background: none;
+ color: orange;
+ top: 0px;
+ height: auto;
+}
+.extended .higher {
+ top: 0px;
+}
+.extended.deeper {
+ height: auto;
+}
+.do .re .mi .fa .sol .la .si {
+ color: cyan;
+}
+.mutli-selector-parents {
+ color: cyan;
+}
+.foo .bar {
+ width: 100%;
+}
+.underParents {
+ color: red;
+}
+.parent .underParents {
+ color: red;
+}
+* + h1 {
+ margin-top: 25px;
+}
+legend + h1 {
+ margin-top: 0;
+}
+h1 + * {
+ margin-top: 10px;
+}
+* + h2 {
+ margin-top: 20px;
+}
+legend + h2 {
+ margin-top: 0;
+}
+h2 + * {
+ margin-top: 8px;
+}
+* + h3 {
+ margin-top: 15px;
+}
+legend + h3 {
+ margin-top: 0;
+}
+h3 + * {
+ margin-top: 5px;
+}
+.error {
+ background-image: "/a.png";
+ background-position: center center;
+}
+.test-rec .recursion {
+ color: black;
+}
+.button {
+ padding-left: 44px;
+}
+.button.large {
+ padding-left: 40em;
+}
--- /dev/null
+#operations {
+ color: #111111;
+ height: 9px;
+ width: 3em;
+ substraction: 0;
+ division: 1;
+}
+#operations .spacing {
+ height: 9px;
+ width: 3em;
+}
+.with-variables {
+ height: 16em;
+ width: 24em;
+ size: 1cm;
+}
+.with-functions {
+ color: #646464;
+ color: #ff8080;
+ color: #c94a4a;
+}
+.negative {
+ height: 0px;
+ width: 4px;
+}
+.shorthands {
+ padding: -1px 2px 0 -4px;
+}
+.rem-dimensions {
+ font-size: 5.5rem;
+}
+.colors {
+ color: #123;
+ border-color: #334455;
+ background-color: #000000;
+}
+.colors .other {
+ color: #222222;
+ border-color: #222222;
+}
+.negations {
+ variable: -4px;
+ variable1: 0px;
+ variable2: 0px;
+ variable3: 8px;
+ variable4: 0px;
+ paren: -4px;
+ paren2: 16px;
+}
--- /dev/null
+.parens {
+ border: 2px solid #000000;
+ margin: 1px 3px 16 3;
+ width: 36;
+ padding: 2px 36px;
+}
+.more-parens {
+ padding: 8 4 4 4px;
+ width-all: 96;
+ width-first: 16 * 6;
+ width-keep: (4 * 4) * 6;
+ height-keep: (7 * 7) + (8 * 8);
+ height-all: 113;
+ height-parts: 49 + 64;
+ margin-keep: (4 * (5 + 5) / 2) - (4 * 2);
+ margin-parts: 20 - 8;
+ margin-all: 12;
+ border-radius-keep: 4px * (1 + 1) / 4 + 3px;
+ border-radius-parts: 8px / 7px;
+ border-radius-all: 5px;
+}
+.negative {
+ neg-var: -1;
+ neg-var-paren: -(1);
+}
+.nested-parens {
+ width: 2 * (4 * (2 + (1 + 6))) - 1;
+ height: ((2 + 3) * (2 + 3) / (9 - 4)) + 1;
+}
+.mixed-units {
+ margin: 2px 4em 1 5pc;
+ padding: 6px 1em 2px 2;
+}
--- /dev/null
+#first > .one {
+ font-size: 2em;
+}
+#first > .one > #second .two > #deux {
+ width: 50%;
+}
+#first > .one > #second .two > #deux #third {
+ height: 100%;
+}
+#first > .one > #second .two > #deux #third:focus {
+ color: black;
+}
+#first > .one > #second .two > #deux #third:focus #fifth > #sixth .seventh #eighth + #ninth {
+ color: purple;
+}
+#first > .one > #second .two > #deux #fourth,
+#first > .one > #second .two > #deux #five,
+#first > .one > #second .two > #deux #six {
+ color: #110000;
+}
+#first > .one > #second .two > #deux #fourth .seven,
+#first > .one > #second .two > #deux #five .seven,
+#first > .one > #second .two > #deux #six .seven,
+#first > .one > #second .two > #deux #fourth .eight > #nine,
+#first > .one > #second .two > #deux #five .eight > #nine,
+#first > .one > #second .two > #deux #six .eight > #nine {
+ border: 1px solid black;
+}
+#first > .one > #second .two > #deux #fourth #ten,
+#first > .one > #second .two > #deux #five #ten,
+#first > .one > #second .two > #deux #six #ten {
+ color: red;
+}
--- /dev/null
+.tiny-scope {
+ color: #998899;
+}
+.scope1 {
+ color: #0000ff;
+ border-color: #000000;
+}
+.scope1 .scope2 {
+ color: #0000ff;
+}
+.scope1 .scope2 .scope3 {
+ color: #ff0000;
+ border-color: #000000;
+ background-color: #ffffff;
+}
+.scope {
+ scoped-val: #008000;
+}
+.heightIsSet {
+ height: 1024px;
+}
+.useHeightInMixinCall {
+ mixin-height: 1024px;
+}
+.imported {
+ exists: true;
+}
+.testImported {
+ exists: true;
+}
+#allAreUsedHere {
+ default: 'top level';
+ scope: 'top level';
+ sub-scope-only: 'inside';
+}
--- /dev/null
+h1 a:hover,
+h2 a:hover,
+h3 a:hover,
+h1 p:hover,
+h2 p:hover,
+h3 p:hover {
+ color: red;
+}
+#all {
+ color: blue;
+}
+#the {
+ color: blue;
+}
+#same {
+ color: blue;
+}
+ul,
+li,
+div,
+q,
+blockquote,
+textarea {
+ margin: 0;
+}
+td {
+ margin: 0;
+ padding: 0;
+}
+td,
+input {
+ line-height: 1em;
+}
+a {
+ color: red;
+}
+a:hover {
+ color: blue;
+}
+div a {
+ color: green;
+}
+p a span {
+ color: yellow;
+}
+.foo .bar .qux,
+.foo .baz .qux {
+ display: block;
+}
+.qux .foo .bar,
+.qux .foo .baz {
+ display: inline;
+}
+.qux.foo .bar,
+.qux.foo .baz {
+ display: inline-block;
+}
+.qux .foo .bar .biz,
+.qux .foo .baz .biz {
+ display: none;
+}
+.a.b.c {
+ color: red;
+}
+.c .b.a {
+ color: red;
+}
+.foo .p.bar {
+ color: red;
+}
+.foo.p.bar {
+ color: red;
+}
+.foo + .foo {
+ background: amber;
+}
+.foo + .foo {
+ background: amber;
+}
+.foo + .foo,
+.foo + .bar,
+.bar + .foo,
+.bar + .bar {
+ background: amber;
+}
+.foo a > .foo a,
+.foo a > .bar a,
+.foo a > .foo b,
+.foo a > .bar b,
+.bar a > .foo a,
+.bar a > .bar a,
+.bar a > .foo b,
+.bar a > .bar b,
+.foo b > .foo a,
+.foo b > .bar a,
+.foo b > .foo b,
+.foo b > .bar b,
+.bar b > .foo a,
+.bar b > .bar a,
+.bar b > .foo b,
+.bar b > .bar b {
+ background: amber;
+}
+.other ::fnord {
+ color: #ff0000;
+}
+.other::fnord {
+ color: #ff0000;
+}
+.other ::bnord {
+ color: #ff0000;
+}
+.other::bnord {
+ color: #ff0000;
+}
+.blood {
+ color: red;
+}
+.bloodred {
+ color: green;
+}
+#blood.blood.red.black {
+ color: black;
+}
+:nth-child(3) {
+ selector: interpolated;
+}
+.test:nth-child(odd):not(:nth-child(3)) {
+ color: #ff0000;
+}
+[prop],
+[prop=10%],
+[prop="value3"],
+[prop*="val3"],
+[|prop~="val3"],
+[*|prop$="val3"],
+[ns|prop^="val3"],
+[3^="val3"],
+[3=3],
+[3] {
+ attributes: yes;
+}
--- /dev/null
+@import "css/background.css";
+/*@import "folder (1)/import-test-d.css";*/
+/*@font-face {
+ src: local(Futura-Medium), url(folder\ \(1\)/fonts.svg#MyGeometricModern) format("svg");
+}*/
+#shorthands {
+ background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
+}
+/*#misc {
+ background-image: url(folder\ \(1\)/images/image.jpg);
+}*/
+#data-uri {
+ background: url(data:image/png;charset=utf-8;base64,
+ kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
+ k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
+ kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
+ background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
+ background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
+}
+#svg-data-uri {
+ background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
+}
+/*.comma-delimited {
+ background: url(folder\ \(1\)/bg.jpg) no-repeat, url(folder\ \(1\)/bg.png) repeat-x top left, url(folder\ \(1\)/bg);
+}
+.values {
+ url: url('folder (1)/Trebuchet');
+}*/
+#logo {
+ width: 100px;
+ height: 100px;
+ background: url('assets/logo.png');
+}
+@font-face {
+ font-family: xecret;
+ src: url('assets/xecret.ttf');
+}
+#secret {
+ font-family: xecret, sans-serif;
+}
--- /dev/null
+#strings {
+ background-image: url("http://son-of-a-banana.com");
+ quotes: "~" "~";
+ content: "#*%:&^,)!.(~*})";
+ empty: "";
+ brackets: "{" "}";
+ escapes: "\"hello\" \\world";
+ escapes2: "\"llo";
+}
+#comments {
+ content: "/* hello */ // not-so-secret";
+}
+#single-quote {
+ quotes: "'" "'";
+ content: '""#!&""';
+ empty: '';
+ semi-colon: ';';
+}
+#escaped {
+ filter: DX.Transform.MS.BS.filter(opacity=50);
+}
+#one-line {
+ image: url(http://tooks.com);
+}
+#crazy {
+ image: url(http://), "}", url("http://}");
+}
+#interpolation {
+ url: "http://lesscss.org/dev/image.jpg";
+ url2: "http://lesscss.org/image-256.jpg";
+ url3: "http://lesscss.org#445566";
+ url4: "http://lesscss.org/hello";
+ url5: "http://lesscss.org/54.4px";
+}
+.mix-mul-class {
+ color: #0000ff;
+ color: #ff0000;
+ color: #000000;
+ color: #ffa500;
+}
+.watermark {
+ family: Univers, Arial, Verdana, San-Serif;
+}
--- /dev/null
+@import "css/background.css";
+@import "import/import-test-d.css";
+@import "file.css";
+@font-face {
+ src: local(Futura-Medium), url(fonts.svg#MyGeometricModern) format("svg");
+}
+#shorthands {
+ background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
+ background: url("img.jpg") center / 100px;
+ background: #ffffff url(image.png) center / 1px 100px repeat-x scroll content-box padding-box;
+}
+#misc {
+ background-image: url(images/image.jpg);
+}
+#data-uri {
+ background: url(data:image/png;charset=utf-8;base64,
+ kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
+ k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
+ kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
+ background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
+ background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
+ background-image: url("http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700");
+}
+#svg-data-uri {
+ background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
+}
+.comma-delimited {
+ background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);
+}
+.values {
+ url: url('Trebuchet');
+}
+#logo {
+ width: 100px;
+ height: 100px;
+ background: url('import/assets/logo.png');
+}
+@font-face {
+ font-family: xecret;
+ src: url('import/assets/xecret.ttf');
+}
+#secret {
+ font-family: xecret, sans-serif;
+}
+#data-uri {
+ uri: url('data:image/jpeg;base64,bm90IGFjdHVhbGx5IGEganBlZyBmaWxlCg==');
+}
+#data-uri-guess {
+ uri: url('data:image/jpeg;base64,bm90IGFjdHVhbGx5IGEganBlZyBmaWxlCg==');
+}
+#data-uri-ascii {
+ uri-1: url('data:text/html,%3Ch1%3EThis%20page%20is%20100%25%20Awesome.%3C%2Fh1%3E%0A');
+ uri-2: url('data:text/html,%3Ch1%3EThis%20page%20is%20100%25%20Awesome.%3C%2Fh1%3E%0A');
+}
+#data-uri-toobig {
+ uri: url('../data/data-uri-fail.png');
+}
+#svg-functions {
+ background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHZpZXdCb3g9IjAgMCAxIDEiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjwvbGluZWFyR3JhZGllbnQ+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkaWVudCkiIC8+PC9zdmc+');
+ background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHZpZXdCb3g9IjAgMCAxIDEiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzAwMDAwMCIvPjxzdG9wIG9mZnNldD0iMyUiIHN0b3AtY29sb3I9IiNmZmE1MDAiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48L2xpbmVhckdyYWRpZW50PjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIGZpbGw9InVybCgjZ3JhZGllbnQpIiAvPjwvc3ZnPg==');
+ background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHZpZXdCb3g9IjAgMCAxIDEiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIxJSIgc3RvcC1jb2xvcj0iI2M0YzRjNCIvPjxzdG9wIG9mZnNldD0iMyUiIHN0b3AtY29sb3I9IiNmZmE1MDAiLz48c3RvcCBvZmZzZXQ9IjUlIiBzdG9wLWNvbG9yPSIjMDA4MDAwIi8+PHN0b3Agb2Zmc2V0PSI5NSUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48L2xpbmVhckdyYWRpZW50PjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIGZpbGw9InVybCgjZ3JhZGllbnQpIiAvPjwvc3ZnPg==');
+}
--- /dev/null
+.variables {
+ width: 14cm;
+}
+.variables {
+ height: 24px;
+ color: #888888;
+ font-family: "Trebuchet MS", Verdana, sans-serif;
+ quotes: "~" "~";
+}
+.redef {
+ zero: 0;
+}
+.redef .inition {
+ three: 3;
+}
+.values {
+ minus-one: -1;
+ font-family: 'Trebuchet', 'Trebuchet', 'Trebuchet';
+ color: #888888 !important;
+ multi: something 'A', B, C, 'Trebuchet';
+}
+.variable-names {
+ name: 'hello';
+}
+.alpha {
+ filter: alpha(opacity=42);
+}
+.testPollution {
+ a: 'no-pollution';
+}
+.units {
+ width: 1px;
+ same-unit-as-previously: 1px;
+ square-pixel-divided: 1px;
+ odd-unit: 2;
+ percentage: 500%;
+ pixels: 500px;
+ conversion-metric-a: 30mm;
+ conversion-metric-b: 3cm;
+ conversion-imperial: 3in;
+ custom-unit: 420octocats;
+ custom-unit-cancelling: 18dogs;
+ mix-units: 2px;
+ invalid-units: 1px;
+}
--- /dev/null
+.whitespace {
+ color: white;
+}
+.whitespace {
+ color: white;
+}
+.whitespace {
+ color: white;
+}
+.whitespace {
+ color: white;
+}
+.whitespace {
+ color: white ;
+}
+.white,
+.space,
+.mania {
+ color: white;
+}
+.no-semi-column {
+ color: #ffffff;
+}
+.no-semi-column {
+ color: white;
+ white-space: pre;
+}
+.no-semi-column {
+ border: 2px solid #ffffff;
+}
+.newlines {
+ background: the,
+ great,
+ wall;
+ border: 2px
+ solid
+ black;
+}
+.sel .newline_ws .tab_ws {
+ color: white;
+ background-position: 45 -23;
+}
--- /dev/null
+not actually a jpeg file
--- /dev/null
+<h1>This page is 100% Awesome.</h1>
--- /dev/null
+/*jshint latedef: nofunc */
+var path = require('path'),
+ fs = require('fs'),
+ sys = require('util');
+
+var less = require('../lib/less');
+var stylize = require('../lib/less/lessc_helper').stylize;
+
+var globals = Object.keys(global);
+
+var oneTestOnly = process.argv[2];
+
+var isVerbose = process.env.npm_config_loglevel === 'verbose';
+
+var totalTests = 0,
+ failedTests = 0,
+ passedTests = 0;
+
+less.tree.functions.add = function (a, b) {
+ return new(less.tree.Dimension)(a.value + b.value);
+};
+less.tree.functions.increment = function (a) {
+ return new(less.tree.Dimension)(a.value + 1);
+};
+less.tree.functions._color = function (str) {
+ if (str.value === "evil red") { return new(less.tree.Color)("600"); }
+};
+
+console.log("\n" + stylize("LESS", 'underline') + "\n");
+
+runTestSet({strictMath: true, relativeUrls: true, silent: true});
+runTestSet({strictMath: true, strictUnits: true}, "errors/",
+ testErrors, null, getErrorPathReplacementFunction("errors"));
+runTestSet({strictMath: true, strictUnits: true, javascriptEnabled: false}, "no-js-errors/",
+ testErrors, null, getErrorPathReplacementFunction("no-js-errors"));
+runTestSet({strictMath: true, dumpLineNumbers: 'comments'}, "debug/", null,
+ function(name) { return name + '-comments'; });
+runTestSet({strictMath: true, dumpLineNumbers: 'mediaquery'}, "debug/", null,
+ function(name) { return name + '-mediaquery'; });
+runTestSet({strictMath: true, dumpLineNumbers: 'all'}, "debug/", null,
+ function(name) { return name + '-all'; });
+runTestSet({strictMath: true, relativeUrls: false, rootpath: "folder (1)/"}, "static-urls/");
+runTestSet({strictMath: true, compress: true}, "compression/");
+runTestSet({}, "legacy/");
+runTestSet({strictMath: true, strictUnits: true, sourceMap: true }, "sourcemaps/",
+ testSourcemap, null, null, function(filename) { return path.join('test/sourcemaps', filename) + '.json'; });
+
+testNoOptions();
+
+function getErrorPathReplacementFunction(dir) {
+ return function(input) {
+ return input.replace(
+ "{path}", path.join(process.cwd(), "/test/less/" + dir + "/"))
+ .replace("{pathrel}", path.join("test", "less", dir + "/"))
+ .replace("{pathhref}", "")
+ .replace("{404status}", "")
+ .replace(/\r\n/g, '\n');
+ };
+}
+
+function testSourcemap(name, err, compiledLess, doReplacements, sourcemap) {
+ fs.readFile(path.join('test/', name) + '.json', 'utf8', function (e, expectedSourcemap) {
+ sys.print("- " + name + ": ");
+ if (sourcemap === expectedSourcemap) {
+ ok('OK');
+ } else if (err) {
+ fail("ERROR: " + (err && err.message));
+ if (isVerbose) {
+ console.error();
+ console.error(err.stack);
+ }
+ } else {
+ difference("FAIL", expectedSourcemap, sourcemap);
+ }
+ });
+}
+
+function testErrors(name, err, compiledLess, doReplacements) {
+ fs.readFile(path.join('test/less/', name) + '.txt', 'utf8', function (e, expectedErr) {
+ sys.print("- " + name + ": ");
+ expectedErr = doReplacements(expectedErr, 'test/less/errors/');
+ if (!err) {
+ if (compiledLess) {
+ fail("No Error", 'red');
+ } else {
+ fail("No Error, No Output");
+ }
+ } else {
+ var errMessage = less.formatError(err);
+ if (errMessage === expectedErr) {
+ ok('OK');
+ } else {
+ difference("FAIL", expectedErr, errMessage);
+ }
+ }
+ });
+}
+
+function globalReplacements(input, directory) {
+ var p = path.join(process.cwd(), directory),
+ pathimport = path.join(process.cwd(), directory + "import/"),
+ pathesc = p.replace(/[.:/\\]/g, function(a) { return '\\' + (a=='\\' ? '\/' : a); }),
+ pathimportesc = pathimport.replace(/[.:/\\]/g, function(a) { return '\\' + (a=='\\' ? '\/' : a); });
+
+ return input.replace(/\{path\}/g, p)
+ .replace(/\{pathesc\}/g, pathesc)
+ .replace(/\{pathimport\}/g, pathimport)
+ .replace(/\{pathimportesc\}/g, pathimportesc)
+ .replace(/\r\n/g, '\n');
+}
+
+function checkGlobalLeaks() {
+ return Object.keys(global).filter(function(v) {
+ return globals.indexOf(v) < 0;
+ });
+}
+
+function runTestSet(options, foldername, verifyFunction, nameModifier, doReplacements, getFilename) {
+ foldername = foldername || "";
+
+ if(!doReplacements) {
+ doReplacements = globalReplacements;
+ }
+
+ fs.readdirSync(path.join('test/less/', foldername)).forEach(function (file) {
+ if (! /\.less/.test(file)) { return; }
+
+ var name = foldername + path.basename(file, '.less');
+
+ if (oneTestOnly && name !== oneTestOnly) {
+ return;
+ }
+
+ totalTests++;
+
+ if (options.sourceMap) {
+ var sourceMapOutput;
+ options.writeSourceMap = function(output) {
+ sourceMapOutput = output;
+ };
+ options.sourceMapOutputFilename = name + ".css";
+ options.sourceMapBasepath = path.join(process.cwd(), "test/less");
+ options.sourceMapRootpath = "testweb/";
+ }
+
+ toCSS(options, path.join('test/less/', foldername + file), function (err, less) {
+
+ if (verifyFunction) {
+ return verifyFunction(name, err, less, doReplacements, sourceMapOutput);
+ }
+ var css_name = name;
+ if(nameModifier) { css_name = nameModifier(name); }
+ fs.readFile(path.join('test/css', css_name) + '.css', 'utf8', function (e, css) {
+ sys.print("- " + css_name + ": ");
+
+ css = css && doReplacements(css, 'test/less/' + foldername);
+ if (less === css) { ok('OK'); }
+ else if (err) {
+ fail("ERROR: " + (err && err.message));
+ if (isVerbose) {
+ console.error();
+ console.error(err.stack);
+ }
+ } else {
+ difference("FAIL", css, less);
+ }
+ });
+ });
+ });
+}
+
+function diff(left, right) {
+ require('diff').diffLines(left, right).forEach(function(item) {
+ if(item.added || item.removed) {
+ var text = item.value.replace("\n", String.fromCharCode(182) + "\n");
+ sys.print(stylize(text, item.added ? 'green' : 'red'));
+ } else {
+ sys.print(item.value);
+ }
+ });
+ console.log("");
+}
+
+function fail(msg) {
+ console.error(stylize(msg, 'red'));
+ failedTests++;
+ endTest();
+}
+
+function difference(msg, left, right) {
+ console.warn(stylize(msg, 'yellow'));
+ failedTests++;
+
+ diff(left, right);
+ endTest();
+}
+
+function ok(msg) {
+ console.log(stylize(msg, 'green'));
+ passedTests++;
+ endTest();
+}
+
+function endTest() {
+ var leaked = checkGlobalLeaks();
+ if (failedTests + passedTests === totalTests) {
+ console.log("");
+ if (failedTests > 0) {
+ console.error(failedTests + stylize(" Failed", "red") + ", " + passedTests + " passed");
+ } else {
+ console.log(stylize("All Passed ", "green") + passedTests + " run");
+ }
+ if (leaked.length > 0) {
+ console.log("");
+ console.warn(stylize("Global leak detected: ", "red") + leaked.join(', '));
+ }
+
+ if (leaked.length || failedTests) {
+ //process.exit(1);
+ process.on('exit', function() { process.reallyExit(1) });
+ }
+ }
+}
+
+function toCSS(options, path, callback) {
+ var css;
+ options = options || {};
+ fs.readFile(path, 'utf8', function (e, str) {
+ if (e) { return callback(e); }
+
+ options.paths = [require('path').dirname(path)];
+ options.filename = require('path').resolve(process.cwd(), path);
+ options.optimization = options.optimization || 0;
+
+ new(less.Parser)(options).parse(str, function (err, tree) {
+ if (err) {
+ callback(err);
+ } else {
+ try {
+ css = tree.toCSS(options);
+ callback(null, css);
+ } catch (e) {
+ callback(e);
+ }
+ }
+ });
+ });
+}
+
+function testNoOptions() {
+ totalTests++;
+ try {
+ sys.print("- Integration - creating parser without options: ");
+ new(less.Parser)();
+ } catch(e) {
+ fail(stylize("FAIL\n", "red"));
+ return;
+ }
+ ok(stylize("OK\n", "green"));
+}
--- /dev/null
+@charset "UTF-8";
+
+@import "import/import-charset-test";
\ No newline at end of file
--- /dev/null
+#yelow {
+ #short {
+ color: #fea;
+ }
+ #long {
+ color: #ffeeaa;
+ }
+ #rgba {
+ color: rgba(255, 238, 170, 0.1);
+ }
+ #argb {
+ color: argb(rgba(255, 238, 170, 0.1));
+ }
+}
+
+#blue {
+ #short {
+ color: #00f;
+ }
+ #long {
+ color: #0000ff;
+ }
+ #rgba {
+ color: rgba(0, 0, 255, 0.1);
+ }
+ #argb {
+ color: argb(rgba(0, 0, 255, 0.1));
+ }
+}
+
+#alpha #hsla {
+ color: hsla(11, 20%, 20%, 0.6);
+}
+
+#overflow {
+ .a { color: (#111111 - #444444); } // #000000
+ .b { color: (#eee + #fff); } // #ffffff
+ .c { color: (#aaa * 3); } // #ffffff
+ .d { color: (#00ee00 + #009900); } // #00ff00
+}
+
+#grey {
+ color: rgb(200, 200, 200);
+}
+
+#333333 {
+ color: rgb(20%, 20%, 20%);
+}
+
+#808080 {
+ color: hsl(50, 0%, 50%);
+}
+
+#00ff00 {
+ color: hsl(120, 100%, 50%);
+}
+
+.lightenblue {
+ color: lighten(blue, 10%);
+}
+
+.darkenblue {
+ color: darken(blue, 10%);
+}
+
+.unknowncolors {
+ color: blue2;
+ border: 2px solid superred;
+}
+
+.transparent {
+ color: transparent;
+ background-color: rgba(0, 0, 0, 0);
+}
+#alpha {
+ @colorvar: rgba(150, 200, 150, 0.7);
+ #fromvar {
+ opacity: alpha(@colorvar);
+ }
+ #short {
+ opacity: alpha(#aaa);
+ }
+ #long {
+ opacity: alpha(#bababa);
+ }
+ #rgba {
+ opacity: alpha(rgba(50, 120, 95, 0.2));
+ }
+ #hsl {
+ opacity: alpha(hsl(120, 100%, 50%));
+ }
+}
--- /dev/null
+/******************\
+* *
+* Comment Header *
+* *
+\******************/
+
+/*
+
+ Comment
+
+*/
+
+/*
+ * Comment Test
+ *
+ * - cloudhead (http://cloudhead.net)
+ *
+ */
+
+////////////////
+@var: "content";
+////////////////
+
+/* Colors
+ * ------
+ * #EDF8FC (background blue)
+ * #166C89 (darkest blue)
+ *
+ * Text:
+ * #333 (standard text) // A comment within a comment!
+ * #1F9EC9 (standard link)
+ *
+ */
+
+/* @group Variables
+------------------- */
+#comments /* boo *//* boo again*/,
+//.commented_out1
+//.commented_out2
+//.commented_out3
+.comments //end of comments1
+//end of comments2
+{
+ /**/ // An empty comment
+ color: red; /* A C-style comment */ /* A C-style comment */
+ background-color: orange; // A little comment
+ font-size: 12px;
+
+ /* lost comment */ content: @var;
+
+ border: 1px solid black;
+
+ // padding & margin //
+ padding: 0; // }{ '"
+ margin: 2em;
+} //
+
+/* commented out
+ #more-comments {
+ color: grey;
+ }
+*/
+
+.selector /* .with */, .lots, /* of */ .comments {
+ color: grey, /* blue */ orange;
+ -webkit-border-radius: 2px /* webkit only */;
+ -moz-border-radius: (2px * 4) /* moz only with operation */;
+}
+
+.mixin_def_with_colors(@a: white, // in
+ @b: 1px //put in @b - causes problems! --->
+ ) // the
+ when (@a = white) {
+ .test {
+ color: @b;
+ }
+}
+.mixin_def_with_colors();
+
+#last { color: blue }
+//
+
+/* *//* { *//* *//* *//* */#div { color:#A33; }/* } */
--- /dev/null
+#colours {
+ color1: #fea;
+ color2: #ffeeaa;
+ color3: rgba(255, 238, 170, 0.1);
+ @color1: #fea;
+ string: "@{color1}";
+ /* comments are stripped */
+ // both types!
+ /*! but not this type
+ Note preserved whitespace
+ */
+}
+dimensions {
+ val: 0.1px;
+ val: 0em;
+ val: 4cm;
+ val: 0.2;
+ val: 5;
+ angles-must-have-unit: 0deg;
+ durations-must-have-unit: 0s;
+ length-doesnt-have-unit: 0px;
+ width: auto\9;
+}
+@page {
+ marks: none;
+@top-left-corner {
+ vertical-align: top;
+}
+@top-left {
+ vertical-align: top;
+}
+}
\ No newline at end of file
--- /dev/null
+.comma-delimited {
+ text-shadow: -1px -1px 1px red, 6px 5px 5px yellow;
+ -moz-box-shadow: 0pt 0pt 2px rgba(255, 255, 255, 0.4) inset,
+ 0pt 4px 6px rgba(255, 255, 255, 0.4) inset;
+ -webkit-transform: rotate(-0.0000000001deg);
+}
+@font-face {
+ font-family: Headline;
+ unicode-range: U+??????, U+0???, U+0-7F, U+A5;
+}
+.other {
+ -moz-transform: translate(0, 11em) rotate(-90deg);
+ transform: rotateX(45deg);
+}
+.item[data-cra_zy-attr1b-ut3=bold] {
+ font-weight: bold;
+}
+p:not([class*="lead"]) {
+ color: black;
+}
+
+input[type="text"].class#id[attr=32]:not(1) {
+ color: white;
+}
+
+div#id.class[a=1][b=2].class:not(1) {
+ color: white;
+}
+
+ul.comma > li:not(:only-child)::after {
+ color: white;
+}
+
+ol.comma > li:nth-last-child(2)::after {
+ color: white;
+}
+
+li:nth-child(4n+1),
+li:nth-child(-5n),
+li:nth-child(-n+2) {
+ color: white;
+}
+
+a[href^="http://"] {
+ color: black;
+}
+
+a[href$="http://"] {
+ color: black;
+}
+
+form[data-disabled] {
+ color: black;
+}
+
+p::before {
+ color: black;
+}
+
+#issue322 {
+ -webkit-animation: anim2 7s infinite ease-in-out;
+}
+
+@-webkit-keyframes frames {
+ 0% { border: 1px }
+ 5.5% { border: 2px }
+ 100% { border: 3px }
+}
+
+@keyframes fontbulger1 {
+ to {
+ font-size: 15px;
+ }
+ from,to {
+ font-size: 12px;
+ }
+ 0%,100% {
+ font-size: 12px;
+ }
+}
+
+.units {
+ font: 1.2rem/2rem;
+ font: 8vw/9vw;
+ font: 10vh/12vh;
+ font: 12vm/15vm;
+ font: 12vmin/15vmin;
+ font: 1.2ch/1.5ch;
+}
+
+@supports ( box-shadow: 2px 2px 2px black ) or
+ ( -moz-box-shadow: 2px 2px 2px black ) {
+ .outline {
+ box-shadow: 2px 2px 2px black;
+ -moz-box-shadow: 2px 2px 2px black;
+ }
+}
+
+@-x-document url-prefix(""github.com"") {
+ h1 {
+ color: red;
+ }
+}
+
+@viewport {
+ font-size: 10px;
+}
+@namespace foo url(http://www.example.com);
+
+foo|h1 { color: blue; }
+foo|* { color: yellow; }
+|h1 { color: red; }
+*|h1 { color: green; }
+h1 { color: green; }
+.upper-test {
+ UpperCaseProperties: allowed;
+}
+@host {
+ div {
+ display: block;
+ }
+}
+::distributed(input::placeholder) {
+ color: #b3b3b3;
+}
\ No newline at end of file
--- /dev/null
+@ugly: fuchsia;
+
+.escape\|random\|char {
+ color: red;
+}
+
+.mixin\!tUp {
+ font-weight: bold;
+}
+
+// class="404"
+.\34 04 {
+ background: red;
+
+ strong {
+ color: @ugly;
+ .mixin\!tUp;
+ }
+}
+
+.trailingTest\+ {
+ color: red;
+}
+
+/* This hideous test of hideousness checks for the selector "blockquote" with various permutations of hex escapes */
+\62\6c\6f \63 \6B \0071 \000075o\74 e {
+ color: silver;
+}
+
+[ng\:cloak],
+ng\:form {
+ display: none;
+}
--- /dev/null
+
+.light when (lightness(@a) > 50%) {
+ color: green;
+}
+.dark when (lightness(@a) < 50%) {
+ color: orange;
+}
+@a: #ddd;
+
+.see-the {
+ @a: #444; // this mirrors what mixins do - they evaluate guards at the point of execution
+ .light();
+ .dark();
+}
+
+.hide-the {
+ .light();
+ .dark();
+}
+
+.multiple-conditions-1 when (@b = 1), (@c = 2), (@d = 3) {
+ color: red;
+}
+
+.multiple-conditions-2 when (@b = 1), (@c = 2), (@d = 2) {
+ color: blue;
+}
+
+@b: 2;
+@c: 3;
+@d: 3;
+
+.inheritance when (@b = 2) {
+ .test {
+ color: black;
+ }
+ &:hover {
+ color: pink;
+ }
+ .hideme when (@b = 1) {
+ color: green;
+ }
+ & when (@b = 1) {
+ hideme: green;
+ }
+}
+
+.hideme when (@b = 1) {
+ .test {
+ color: black;
+ }
+ &:hover {
+ color: pink;
+ }
+ .hideme when (@b = 1) {
+ color: green;
+ }
+}
+
+& when (@b = 1) {
+ .hideme {
+ color: red;
+ }
+}
\ No newline at end of file
--- /dev/null
+@charset "utf-8";
+div { color: black; }
+div { width: 99%; }
+
+* {
+ min-width: 45em;
+}
+
+h1, h2 > a > p, h3 {
+ color: none;
+}
+
+div.class {
+ color: blue;
+}
+
+div#id {
+ color: green;
+}
+
+.class#id {
+ color: purple;
+}
+
+.one.two.three {
+ color: grey;
+}
+
+@media print {
+ * {
+ font-size: 3em;
+ }
+}
+
+@media screen {
+ * {
+ font-size: 10px;
+ }
+}
+
+@font-face {
+ font-family: 'Garamond Pro';
+}
+
+a:hover, a:link {
+ color: #999;
+}
+
+p, p:first-child {
+ text-transform: none;
+}
+
+q:lang(no) {
+ quotes: none;
+}
+
+p + h1 {
+ font-size: +2.2em;
+}
+
+#shorthands {
+ border: 1px solid #000;
+ font: 12px/16px Arial;
+ font: 100%/16px Arial;
+ margin: 1px 0;
+ padding: 0 auto;
+}
+
+#more-shorthands {
+ margin: 0;
+ padding: 1px 0 2px 0;
+ font: normal small/20px 'Trebuchet MS', Verdana, sans-serif;
+ font: 0/0 a;
+ border-radius: 5px / 10px;
+}
+
+.misc {
+ -moz-border-radius: 2px;
+ display: -moz-inline-stack;
+ width: .1em;
+ background-color: #009998;
+ background: -webkit-gradient(linear, left top, left bottom, from(red), to(blue));
+ margin: ;
+ .nested-multiple {
+ multiple-semi-colons: yes;;;;;;
+ };
+ filter: alpha(opacity=100);
+ width: auto\9;
+}
+
+#important {
+ color: red !important;
+ width: 100%!important;
+ height: 20px ! important;
+}
+
+.def-font(@name) {
+ @font-face {
+ font-family: @name
+ }
+}
+
+.def-font(font-a);
+.def-font(font-b);
+
+.æøå {
+ margin: 0;
+}
--- /dev/null
+@charset "ISO-8859-1";
+
+.mixin_import1() {
+ @media all {
+ .tst {
+ color: black;
+ @media screen {
+ color: red;
+ .tst3 {
+ color: white;
+ }
+ }
+ }
+ }
+}
+
+.mixin_import2() {
+ .tst2 {
+ color: white;
+ }
+}
+
+.tst3 {
+ color: grey;
+}
\ No newline at end of file
--- /dev/null
+@charset "UTF-8";
+
+@import "import/test.less";
+
+.start() {
+ .test2 {
+ color: red;
+ }
+}
+
+.mix() {
+ color: black;
+}
+
+.test1 {
+ .mix();
+}
+
+.start();
+
+.mixin_import1();
+
+.mixin_import2();
\ No newline at end of file
--- /dev/null
+.a {
+ error: (1px + 3em);
+}
\ No newline at end of file
--- /dev/null
+SyntaxError: Incompatible units. Change the units or use the unit function. Bad units: 'px' and 'em'. in {path}add-mixed-units.less on line null, column 0:
+1 error: (1px + 3em);
--- /dev/null
+.a {
+ error: ((1px * 2px) + (3em * 3px));
+}
\ No newline at end of file
--- /dev/null
+SyntaxError: Incompatible units. Change the units or use the unit function. Bad units: 'px*px' and 'em*px'. in {path}add-mixed-units2.less on line null, column 0:
+1 error: ((1px * 2px) + (3em * 3px));
--- /dev/null
+@@demo: "hi";
\ No newline at end of file
--- /dev/null
+ParseError: Unrecognised input in {path}bad-variable-declaration1.less on line 1, column 1:
+1 @@demo: "hi";
--- /dev/null
+.test {
+ color: color("NOT A COLOR");
+}
\ No newline at end of file
--- /dev/null
+ArgumentError: error evaluating function `color`: argument must be a color keyword or 3/6 digit hex e.g. #FFF in {path}color-func-invalid-color.less on line 2, column 10:
+1 .test {
+2 color: color("NOT A COLOR");
+3 }
--- /dev/null
+.a {
+ prop: (3 / #fff);
+}
\ No newline at end of file
--- /dev/null
+OperationError: Can't substract or divide a color from a number in {path}color-operation-error.less on line null, column 0:
+1 prop: (3 / #fff);
--- /dev/null
+#gaga /* Comment */ span { color: red }
\ No newline at end of file
--- /dev/null
+ParseError: Unrecognised input in {path}comment-in-selector.less on line 1, column 21:
+1 #gaga /* Comment */ span { color: red }
--- /dev/null
+.a {
+ error: (1px / 3em);
+}
\ No newline at end of file
--- /dev/null
+SyntaxError: Multiple units in dimension. Correct the units or use the unit function. Bad unit: px/em in {path}divide-mixed-units.less on line 2, column 3:
+1 .a {
+2 error: (1px / 3em);
+3 }
--- /dev/null
+:extend(.a all) {
+ property: red;
+}
\ No newline at end of file
--- /dev/null
+SyntaxError: Extend must be used to extend a selector, it cannot be used on its own in {path}extend-no-selector.less on line 1, column 17:
+1 :extend(.a all) {
+2 property: red;
--- /dev/null
+.a:extend(.b all).c {
+ property: red;
+}
\ No newline at end of file
--- /dev/null
+SyntaxError: Extend can only be used at the end of selector in {path}extend-not-at-end.less on line 1, column 21:
+1 .a:extend(.b all).c {
+2 property: red;
--- /dev/null
+.a {
+ color: green;
+ // tests line number for import reference is correct
+}
+
+@import "file-does-not-exist.less";
\ No newline at end of file
--- /dev/null
+FileError: '{pathhref}file-does-not-exist.less' wasn't found{404status} in {path}import-missing.less on line 6, column 1:
+5
+6 @import "file-does-not-exist.less";
--- /dev/null
+@import "this-statement-is-invalid.less"
\ No newline at end of file
--- /dev/null
+ParseError: Unrecognised input in {path}import-no-semi.less on line 1, column 1:
+1 @import "this-statement-is-invalid.less"
--- /dev/null
+@import "imports/import-subfolder1.less";
\ No newline at end of file
--- /dev/null
+NameError: .mixin-not-defined is undefined in {path}mixin-not-defined.less on line 11, column 1:
+10
+11 .mixin-not-defined();
--- /dev/null
+@import "imports/import-subfolder2.less";
\ No newline at end of file
--- /dev/null
+ParseError: missing opening `{` in {path}parse-error-curly-bracket.less on line 1, column 2:
+1 }}
--- /dev/null
+@import "subfolder/mixin-not-defined.less";
\ No newline at end of file
--- /dev/null
+@import "subfolder/parse-error-curly-bracket.less";
\ No newline at end of file
--- /dev/null
+.someclass
+{
+ font-weight: bold;
+}
\ No newline at end of file
--- /dev/null
+@import "../../mixin-not-defined.less";
\ No newline at end of file
--- /dev/null
+@import "../../parse-error-curly-bracket.less";
\ No newline at end of file
--- /dev/null
+.scope {
+ var: `this.foo.toJS()`;
+}
--- /dev/null
+SyntaxError: JavaScript evaluation error: 'TypeError: Cannot call method 'toJS' of undefined' in {path}javascript-error.less on line 2, column 27:
+1 .scope {
+2 var: `this.foo.toJS()`;
+3 }
--- /dev/null
+.mixin(@a : 4, @b : 3, @c: 2) {
+ will: fail;
+}
+.mixin-test {
+ .mixin(@a: 5; @b: 6, @c: 7);
+}
\ No newline at end of file
--- /dev/null
+SyntaxError: Cannot mix ; and , as delimiter types in {path}mixed-mixin-definition-args-1.less on line 5, column 30:
+4 .mixin-test {
+5 .mixin(@a: 5; @b: 6, @c: 7);
+6 }
--- /dev/null
+.mixin(@a : 4, @b : 3, @c: 2) {
+ will: fail;
+}
+.mixin-test {
+ .mixin(@a: 5, @b: 6; @c: 7);
+}
--- /dev/null
+SyntaxError: Cannot mix ; and , as delimiter types in {path}mixed-mixin-definition-args-2.less on line 5, column 26:
+4 .mixin-test {
+5 .mixin(@a: 5, @b: 6; @c: 7);
+6 }
--- /dev/null
+
+.error-is-further-on() {
+}
+
+.pad-here-to-reproduce-error-in() {
+}
+
+.the-import-subfolder-test() {
+}
+
+.mixin-not-defined();
\ No newline at end of file
--- /dev/null
+NameError: .mixin-not-defined is undefined in {path}mixin-not-defined.less on line 11, column 1:
+10
+11 .mixin-not-defined();
--- /dev/null
+@saxofon:trumpete;
+
+.mixin(saxofon) {
+}
+
+.mixin(@saxofon);
\ No newline at end of file
--- /dev/null
+RuntimeError: No matching definition was found for `.mixin(trumpete)` in {path}mixin-not-matched.less on line 6, column 1:
+5
+6 .mixin(@saxofon);
--- /dev/null
+@saxofon:trumpete;
+
+.mixin(@a, @b) {
+}
+
+.mixin(@a: @saxofon);
\ No newline at end of file
--- /dev/null
+RuntimeError: No matching definition was found for `.mixin(@a:trumpete)` in {path}mixin-not-matched2.less on line 6, column 1:
+5
+6 .mixin(@a: @saxofon);
--- /dev/null
+@ie8: true;
+.a when (@ie8 = true),
+.b {
+}
\ No newline at end of file
--- /dev/null
+SyntaxError: Guards are only currently allowed on a single selector. in {path}multiple-guards-on-css-selectors.less on line 3, column 1:
+2 .a when (@ie8 = true),
+3 .b {
+4 }
--- /dev/null
+/* Test */
+#blah {
+ // blah
+}
+.a {
+ error: (1px * 1em);
+}
\ No newline at end of file
--- /dev/null
+SyntaxError: Multiple units in dimension. Correct the units or use the unit function. Bad unit: em*px in {path}multiply-mixed-units.less on line 6, column 3:
+5 .a {
+6 error: (1px * 1em);
+7 }
--- /dev/null
+.a {
+ something: (12 (13 + 5 -23) + 5);
+}
\ No newline at end of file
--- /dev/null
+SyntaxError: expected ')' got '(' in {path}parens-error-1.less on line 2, column 18:
+1 .a {
+2 something: (12 (13 + 5 -23) + 5);
+3 }
--- /dev/null
+.a {
+ something: (12 * (13 + 5 -23));
+}
\ No newline at end of file
--- /dev/null
+SyntaxError: expected ')' got '-' in {path}parens-error-2.less on line 2, column 28:
+1 .a {
+2 something: (12 * (13 + 5 -23));
+3 }
--- /dev/null
+.a {
+ something: (12 + (13 + 10 -23));
+}
\ No newline at end of file
--- /dev/null
+SyntaxError: expected ')' got '-' in {path}parens-error-3.less on line 2, column 29:
+1 .a {
+2 something: (12 + (13 + 10 -23));
+3 }
--- /dev/null
+}}
\ No newline at end of file
--- /dev/null
+ParseError: missing opening `{` in {path}parse-error-curly-bracket.less on line 1, column 2:
+1 }}
--- /dev/null
+body {
+ background-color: #fff;
--- /dev/null
+ParseError: missing closing `}` in {path}parse-error-missing-bracket.less on line 3, column 1:
+2 background-color: #fff;
+3
--- /dev/null
+@import 'import/import-test.less';
+
+body
+{
+ font-family: arial, sans-serif;
+}
+
+nonsense;
+
+.clickable
+{
+ cursor: pointer;
+}
\ No newline at end of file
--- /dev/null
+ParseError: Unrecognised input in {path}parse-error-with-import.less on line 8, column 9:
+7
+8 nonsense;
+9
--- /dev/null
+.test {
+ display/*/: block; /*sorry for IE5*/
+}
\ No newline at end of file
--- /dev/null
+ParseError: Unrecognised input in {path}property-ie5-hack.less on line 2, column 3:
+1 .test {
+2 display/*/: block; /*sorry for IE5*/
+3 }
--- /dev/null
+.a() {
+ prop:1;
+}
+.a();
\ No newline at end of file
--- /dev/null
+SyntaxError: properties must be inside selector blocks, they cannot be in the root. in {path}property-in-root.less on line 2, column 3:
+1 .a() {
+2 prop:1;
+3 }
--- /dev/null
+@import "property-in-root";
\ No newline at end of file
--- /dev/null
+SyntaxError: properties must be inside selector blocks, they cannot be in the root. in {path}property-in-root.less on line 2, column 3:
+1 .a() {
+2 prop:1;
+3 }
--- /dev/null
+prop:1;
+.a {
+ prop:1;
+}
\ No newline at end of file
--- /dev/null
+SyntaxError: properties must be inside selector blocks, they cannot be in the root. in {path}property-in-root3.less on line 1, column 1:
+1 prop:1;
+2 .a {
--- /dev/null
+@bodyColor: darken(@bodyColor, 30%);
\ No newline at end of file
--- /dev/null
+NameError: Recursive variable definition for @bodyColor in {path}recursive-variable.less on line 1, column 20:
+1 @bodyColor: darken(@bodyColor, 30%);
--- /dev/null
+.a {
+ a: svg-gradient(horizontal, black, white);
+}
\ No newline at end of file
--- /dev/null
+ArgumentError: error evaluating function `svg-gradient`: svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center' in {path}svg-gradient1.less on line 2, column 6:
+1 .a {
+2 a: svg-gradient(horizontal, black, white);
+3 }
--- /dev/null
+.a {
+ a: svg-gradient(to bottom, black, orange, 45%, white);
+}
\ No newline at end of file
--- /dev/null
+ArgumentError: error evaluating function `svg-gradient`: svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position] in {path}svg-gradient2.less on line 2, column 6:
+1 .a {
+2 a: svg-gradient(to bottom, black, orange, 45%, white);
+3 }
--- /dev/null
+.a {
+ a: svg-gradient(black, orange);
+}
\ No newline at end of file
--- /dev/null
+ArgumentError: error evaluating function `svg-gradient`: svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position] in {path}svg-gradient3.less on line 2, column 6:
+1 .a {
+2 a: svg-gradient(black, orange);
+3 }
--- /dev/null
+.a {
+ font-size: unit(80/16,rem);
+}
\ No newline at end of file
--- /dev/null
+ArgumentError: error evaluating function `unit`: the first argument to unit must be a number. Have you forgotten parenthesis? in {path}unit-function.less on line 2, column 14:
+1 .a {
+2 font-size: unit(80/16,rem);
+3 }
--- /dev/null
+//very simple chaining
+.a {
+ color: black;
+}
+.b:extend(.a) {}
+.c:extend(.b) {}
+
+//very simple chaining, ordering not important
+
+.d:extend(.e) {}
+.e:extend(.f) {}
+.f {
+ color: black;
+}
+
+//extend with all
+
+.g.h {
+ color: black;
+}
+.i.j:extend(.g all) {
+ color: white;
+}
+.k:extend(.i all) {}
+
+//extend multi-chaining
+
+.l {
+ color: black;
+}
+.m:extend(.l){}
+.n:extend(.m){}
+.o:extend(.n){}
+.p:extend(.o){}
+.q:extend(.p){}
+.r:extend(.q){}
+.s:extend(.r){}
+.t:extend(.s){}
+
+// self referencing is ignored
+
+.u {color: black;}
+.v.u.v:extend(.u all){}
+
+// circular reference because the new extend product will match the existing extend
+
+.w:extend(.w) {color: black;}
+.v.w.v:extend(.w all){}
+
+// classic circular references
+
+.x:extend(.z) {
+ color: x;
+}
+.y:extend(.x) {
+ color: y;
+}
+.z:extend(.y) {
+ color: z;
+}
+
+//very simple chaining, but with the extend inside the ruleset
+.va {
+ color: black;
+}
+.vb {
+ &:extend(.va);
+ color: white;
+}
+.vc {
+ &:extend(.vb);
+}
+
+// media queries - dont extend outside, do extend inside
+
+@media tv {
+ .ma:extend(.a,.b,.c,.d,.e,.f,.g,.h,.i,.j,.k,.l,.m,.n,.o,.p,.q,.r,.s,.t,.u,.v,.w,.x,.y,.z,.md) {
+ color: black;
+ }
+ .md {
+ color: white;
+ }
+ @media plasma {
+ .me, .mf {
+ &:extend(.mb,.md);
+ background: red;
+ }
+ }
+}
+.mb:extend(.ma) {};
+.mc:extend(.mb) {};
\ No newline at end of file
--- /dev/null
+.clearfix {
+ *zoom: 1;
+ &:after {
+ content: '';
+ display: block;
+ clear: both;
+ height: 0;
+ }
+}
+
+.foo {
+ &:extend(.clearfix all);
+ color: red;
+}
+
+.bar {
+ &:extend(.clearfix all);
+ color: blue;
+}
--- /dev/null
+.replace.replace,
+.c.replace + .replace {
+ .replace,
+ .c {
+ prop: copy-paste-replace;
+ }
+}
+.rep_ace:extend(.replace.replace .replace) {}
+
+.a .b .c {
+ prop: not_effected;
+}
+
+.a {
+ prop: is_effected;
+ .b {
+ prop: not_effected;
+ }
+ .b.c {
+ prop: not_effected;
+ }
+}
+
+.c, .a {
+ .b, .a {
+ .a, .c {
+ prop: not_effected;
+ }
+ }
+}
+
+.effected {
+ &:extend(.a);
+ &:extend(.b);
+ &:extend(.c);
+}
+
+.e {
+ && {
+ prop: extend-double;
+ &:hover {
+ hover: not-extended;
+ }
+ }
+}
+.dbl:extend(.e.e) {}
--- /dev/null
+.ext1 .ext2 {
+ background: black;
+}
+
+@media tv {
+ .ext1 .ext3 {
+ color: white;
+ }
+ .tv-lowres :extend(.ext1 all) {
+ background: blue;
+ }
+ @media hires {
+ .ext1 .ext4 {
+ color: green;
+ }
+ .tv-hires :extend(.ext1 all) {
+ background: red;
+ }
+ }
+}
+
+.all:extend(.ext1 all) {
+
+}
\ No newline at end of file
--- /dev/null
+.sidebar {
+ width: 300px;
+ background: red;
+
+ .box {
+ background: #FFF;
+ border: 1px solid #000;
+ margin: 10px 0;
+ }
+}
+
+.sidebar2 {
+ &:extend(.sidebar all);
+ background: blue;
+}
+
+.type1 {
+ .sidebar3 {
+ &:extend(.sidebar all);
+ background: green;
+ }
+}
+
+.type2 {
+ &.sidebar4 {
+ &:extend(.sidebar all);
+ background: red;
+ }
+}
+
+.button {
+ color: black;
+ &:hover {
+ color: white;
+ }
+}
+.submit {
+ &:extend(.button);
+ &:hover:extend(.button:hover) {}
+}
+
+.nomatch {
+ &:hover:extend(.button :hover) {}
+}
+
+.button2 {
+ :hover {
+ nested: white;
+ }
+}
+.button2 :hover {
+ notnested: black;
+}
+
+.nomatch :extend(.button2:hover) {}
+
+.amp-test-a,
+.amp-test-b {
+ .amp-test-c &.amp-test-d&.amp-test-e {
+ .amp-test-f&+&.amp-test-g:extend(.amp-test-h) {}
+ }
+}
+.amp-test-h {
+ test: extended by masses of selectors;
+}
\ No newline at end of file
--- /dev/null
+.error {
+ border: 1px #f00;
+ background: #fdd;
+}
+.error.intrusion {
+ font-size: 1.3em;
+ font-weight: bold;
+}
+.intrusion .error {
+ display: none;
+}
+.badError:extend(.error all) {
+ border-width: 3px;
+}
+
+.foo .bar, .foo .baz {
+ display: none;
+}
+
+.ext1 .ext2
+ :extend(.foo all) {
+}
+
+.ext3:extend(.foo all),
+.ext4:extend(.foo all) {
+}
+
+div.ext5,
+.ext6 > .ext5 {
+ width: 100px;
+}
+
+.should-not-exist-in-output,
+.ext7:extend(.ext5 all) {
+}
+
+.ext {
+ test: 1;
+}
+// same as
+// .a .c:extend(.ext all)
+// .b .c:extend(.ext all)
+// .a .c .d
+// .b .c .d
+.a, .b {
+ test: 2;
+ .c:extend(.ext all) {
+ test: 3;
+ .d {
+ test: 4;
+ }
+ }
+}
+
+.replace.replace,
+.c.replace + .replace {
+ .replace,
+ .c {
+ prop: copy-paste-replace;
+ }
+}
+.rep_ace:extend(.replace all) {}
+
+.attributes {
+ [data="test"] {
+ extend: attributes;
+ }
+ .attribute-test {
+ &:extend([data="test"] all);
+ }
+ [data] {
+ extend: attributes2;
+ }
+ .attribute-test2 {
+ &:extend([data] all); //you could argue it should match [data="test"]... not for now though...
+ }
+ @attr-data: "test3";
+ [data=@{attr-data}] {
+ extend: attributes2;
+ }
+ .attribute-test {
+ &:extend([data="test3"] all);
+ }
+}
+
+.header {
+ .header-nav {
+ background: red;
+ &:before {
+ background: blue;
+ }
+ }
+}
+
+.footer {
+ .footer-nav {
+ &:extend( .header .header-nav all );
+ }
+}
\ No newline at end of file
--- /dev/null
+.error {
+ border: 1px #f00;
+ background: #fdd;
+}
+.error.intrusion {
+ font-size: 1.3em;
+ font-weight: bold;
+}
+.intrusion .error {
+ display: none;
+}
+.badError {
+ &:extend(.error all);
+ border-width: 3px;
+}
+
+.foo .bar, .foo .baz {
+ display: none;
+}
+
+.ext1 .ext2 {
+ &:extend(.foo all);
+}
+
+.ext3,
+.ext4 {
+ &:extend(.foo all);
+ &:extend(.bar all);
+}
+
+div.ext5,
+.ext6 > .ext5 {
+ width: 100px;
+}
+
+.ext7 {
+ &:extend(.ext5 all);
+}
+
+.ext8.ext9 {
+ result: add-foo;
+}
+.ext8 .ext9,
+.ext8 + .ext9,
+.ext8 > .ext9 {
+ result: bar-matched;
+}
+.ext8.nomatch {
+ result: none;
+}
+.ext8 {
+ .ext9 {
+ result: match-nested-bar;
+ }
+}
+.ext8 {
+ &.ext9 {
+ result: match-nested-foo;
+ }
+}
+
+.fuu:extend(.ext8.ext9 all) {}
+.buu:extend(.ext8 .ext9 all) {}
+.zap:extend(.ext8 + .ext9 all) {}
+.zoo:extend(.ext8 > .ext9 all) {}
+
+.aa {
+ color: black;
+ .dd {
+ background: red;
+ }
+}
+.bb {
+ background: red;
+ .bb {
+ color: black;
+ }
+}
+.cc:extend(.aa,.bb) {}
+.ee:extend(.dd all,.bb) {}
+.ff:extend(.dd,.bb all) {}
\ No newline at end of file
--- /dev/null
+
+// simple array/list:
+
+.multiunit {
+ @v: abc "abc" 1 1px 1% #123;
+ length: length(@v);
+ extract: extract(@v, 1) extract(@v, 2) extract(@v, 3) extract(@v, 4) extract(@v, 5) extract(@v, 6);
+}
+
+.incorrect-index {
+ @v1: a b c;
+ @v2: a, b, c;
+ v1: extract(@v1, 5);
+ v2: extract(@v2, -2);
+}
+
+.scalar {
+ @var: variable;
+ var-value: extract(@var, 1);
+ var-length: length(@var);
+ ill-index: extract(@var, 2);
+
+ name-value: extract(name, 1);
+ string-value: extract("string", 1);
+ number-value: extract(12345678, 1);
+ color-value: extract(blue, 1);
+ rgba-value: extract(rgba(80, 160, 240, 0.67), 1);
+ empty-value: extract(~'', 1);
+
+ name-length: length(name);
+ string-length: length("string");
+ number-length: length(12345678);
+ color-length: length(blue);
+ rgba-length: length(rgba(80, 160, 240, 0.67));
+ empty-length: length(~'');
+}
+
+.mixin-arguments {
+ .mixin-args(a b c d);
+ .mixin-args(a, b, c, d);
+ .mixin-args(1; 2; 3; 4);
+}
+
+.mixin-args(@value) {
+ &-1 {
+ length: length(@value);
+ extract: extract(@value, 3) ~"|" extract(@value, 2) ~"|" extract(@value, 1);
+ }
+}
+
+.mixin-args(...) {
+ &-2 {
+ length: length(@arguments);
+ extract: extract(@arguments, 3) ~"|" extract(@arguments, 2) ~"|" extract(@arguments, 1);
+ }
+}
+
+.mixin-args(@values...) {
+ &-3 {
+ length: length(@values);
+ extract: extract(@values, 3) ~"|" extract(@values, 2) ~"|" extract(@values, 1);
+ }
+}
+
+.mixin-args(@head, @tail...) {
+ &-4 {
+ length: length(@tail);
+ extract: extract(@tail, 2) ~"|" extract(@tail, 1);
+ }
+}
+
+// "multidimensional" array/list
+
+.md-space-comma {
+ @v: a b c, 1 2 3, "x" "y" "z";
+ length-1: length(@v);
+ extract-1: extract(@v, 2);
+ length-2: length(extract(@v, 2));
+ extract-2: extract(extract(@v, 2), 2);
+
+ &-as-args {.mixin-args(a b c, 1 2 3, "x" "y" "z")}
+}
+
+.md-cat-space-comma {
+ @a: a b c;
+ @b: 1 2 3;
+ @c: "x" "y" "z";
+ @v: @a, @b, @c;
+ length-1: length(@v);
+ extract-1: extract(@v, 2);
+ length-2: length(extract(@v, 2));
+ extract-2: extract(extract(@v, 2), 2);
+
+ &-as-args {.mixin-args(@a, @b, @c)}
+}
+
+.md-cat-comma-space {
+ @a: a, b, c;
+ @b: 1, 2, 3;
+ @c: "x", "y", "z";
+ @v: @a @b @c;
+ length-1: length(@v);
+ extract-1: extract(@v, 2);
+ length-2: length(extract(@v, 2));
+ extract-2: extract(extract(@v, 2), 2);
+
+ &-as-args {.mixin-args(@a @b @c)}
+}
+
+.md-3D {
+ @a: a b c d, 1 2 3 4;
+ @b: 5 6 7 8, e f g h;
+ .3D(@a, @b);
+
+ .3D(...) {
+
+ @v1: @arguments;
+ length-1: length(@v1);
+ extract-1: extract(@v1, 1);
+
+ @v2: extract(@v1, 2);
+ length-2: length(@v2);
+ extract-2: extract(@v2, 1);
+
+ @v3: extract(@v2, 1);
+ length-3: length(@v3);
+ extract-3: extract(@v3, 3);
+
+ @v4: extract(@v3, 4);
+ length-4: length(@v4);
+ extract-4: extract(@v4, 1);
+ }
+}
--- /dev/null
+#functions {
+ @var: 10;
+ @colors: #000, #fff;
+ color: _color("evil red"); // #660000
+ width: increment(15);
+ height: undefined("self");
+ border-width: add(2, 3);
+ variable: increment(@var);
+ background: linear-gradient(@colors);
+}
+
+#built-in {
+ @r: 32;
+ escaped: e("-Some::weird(#thing, y)");
+ lighten: lighten(#ff0000, 40%);
+ darken: darken(#ff0000, 40%);
+ saturate: saturate(#29332f, 20%);
+ desaturate: desaturate(#203c31, 20%);
+ greyscale: greyscale(#203c31);
+ hsl-clamp: hsl(380, 150%, 150%);
+ spin-p: spin(hsl(340, 50%, 50%), 40);
+ spin-n: spin(hsl(30, 50%, 50%), -40);
+ luma-white: luma(#fff);
+ luma-black: luma(#000);
+ luma-black-alpha: luma(rgba(0,0,0,0.5));
+ luma-red: luma(#ff0000);
+ luma-green: luma(#00ff00);
+ luma-blue: luma(#0000ff);
+ luma-yellow: luma(#ffff00);
+ luma-cyan: luma(#00ffff);
+ luma-white-alpha: luma(rgba(255,255,255,0.5));
+ contrast-filter: contrast(30%);
+ saturate-filter: saturate(5%);
+ contrast-white: contrast(#fff);
+ contrast-black: contrast(#000);
+ contrast-red: contrast(#ff0000);
+ contrast-green: contrast(#00ff00);
+ contrast-blue: contrast(#0000ff);
+ contrast-yellow: contrast(#ffff00);
+ contrast-cyan: contrast(#00ffff);
+ contrast-light: contrast(#fff, #111111, #eeeeee);
+ contrast-dark: contrast(#000, #111111, #eeeeee);
+ contrast-wrongorder: contrast(#fff, #eeeeee, #111111, 0.5);
+ contrast-light-thresh: contrast(#fff, #111111, #eeeeee, 0.5);
+ contrast-dark-thresh: contrast(#000, #111111, #eeeeee, 0.5);
+ contrast-high-thresh: contrast(#555, #111111, #eeeeee, 0.6);
+ contrast-low-thresh: contrast(#555, #111111, #eeeeee, 0.1);
+ contrast-light-thresh-per: contrast(#fff, #111111, #eeeeee, 50%);
+ contrast-dark-thresh-per: contrast(#000, #111111, #eeeeee, 50%);
+ contrast-high-thresh-per: contrast(#555, #111111, #eeeeee, 60%);
+ contrast-low-thresh-per: contrast(#555, #111111, #eeeeee, 10%);
+ format: %("rgb(%d, %d, %d)", @r, 128, 64);
+ format-string: %("hello %s", "world");
+ format-multiple: %("hello %s %d", "earth", 2);
+ format-url-encode: %('red is %A', #ff0000);
+ eformat: e(%("rgb(%d, %d, %d)", @r, 128, 64));
+
+ unitless: unit(12px);
+ unit: unit((13px + 1px), em);
+
+ hue: hue(hsl(98, 12%, 95%));
+ saturation: saturation(hsl(98, 12%, 95%));
+ lightness: lightness(hsl(98, 12%, 95%));
+ hsvhue: hsvhue(hsv(98, 12%, 95%));
+ hsvsaturation: hsvsaturation(hsv(98, 12%, 95%));
+ hsvvalue: hsvvalue(hsv(98, 12%, 95%));
+ red: red(#f00);
+ green: green(#0f0);
+ blue: blue(#00f);
+ rounded: round((@r/3));
+ rounded-two: round((@r/3), 2);
+ roundedpx: round((10px / 3));
+ roundedpx-three: round((10px / 3), 3);
+ rounded-percentage: round(10.2%);
+ ceil: ceil(10.1px);
+ floor: floor(12.9px);
+ sqrt: sqrt(25px);
+ pi: pi();
+ mod: mod(13m, 11cm); // could take into account units, doesn't at the moment
+ abs: abs(-4%);
+ tan: tan(42deg);
+ sin: sin(10deg);
+ cos: cos(12);
+ atan: atan(tan(0.1rad));
+ atan: convert(acos(cos(34deg)), deg);
+ atan: convert(acos(cos(50grad)), deg);
+ pow: pow(8px, 2);
+ pow: pow(4, 3);
+ pow: pow(3, 3em);
+ min: min(0);
+ min: min("junk", 6, 5);
+ min: min(1pc, 3pt);
+ max: max(1, 3);
+ max: max(3%, 1cm, 8%, 2mm);
+ percentage: percentage((10px / 50));
+ color: color("#ff0011");
+ tint: tint(#777777, 13);
+ tint-full: tint(#777777, 100);
+ tint-percent: tint(#777777, 13%);
+ shade: shade(#777777, 13);
+ shade-full: shade(#777777, 100);
+ shade-percent: shade(#777777, 13%);
+
+ fade-out: fadeOut(red, 5%); // support fadeOut and fadeout
+ fade-in: fadein(fadeout(red, 10%), 5%);
+
+ hsv: hsv(5, 50%, 30%);
+ hsva: hsva(3, 50%, 30%, 0.2);
+
+ mix: mix(#ff0000, #ffff00, 80);
+ mix-0: mix(#ff0000, #ffff00, 0);
+ mix-100: mix(#ff0000, #ffff00, 100);
+ mix-weightless: mix(#ff0000, #ffff00);
+ mixt: mix(#ff0000, transparent);
+
+ .is-a {
+ color: iscolor(#ddd);
+ color1: iscolor(red);
+ color2: iscolor(rgb(0, 0, 0));
+ color3: iscolor(transparent);
+ keyword: iskeyword(hello);
+ number: isnumber(32);
+ string: isstring("hello");
+ pixel: ispixel(32px);
+ percent: ispercentage(32%);
+ em: isem(32em);
+ cat: isunit(32cat, cat);
+ }
+}
+
+#alpha {
+ alpha: darken(hsla(25, 50%, 50%, 0.6), 10%);
+ alpha2: alpha(rgba(3, 4, 5, 0.5));
+ alpha3: alpha(transparent);
+}
+
+#blendmodes {
+ multiply: multiply(#f60000, #f60000);
+ screen: screen(#f60000, #0000f6);
+ overlay: overlay(#f60000, #0000f6);
+ softlight: softlight(#f60000, #ffffff);
+ hardlight: hardlight(#f60000, #0000f6);
+ difference: difference(#f60000, #0000f6);
+ exclusion: exclusion(#f60000, #0000f6);
+ average: average(#f60000, #0000f6);
+ negation: negation(#f60000, #313131);
+}
+
+#extract-and-length {
+ @anon: A B C 1 2 3;
+ extract: extract(@anon, 6) extract(@anon, 5) extract(@anon, 4) extract(@anon, 3) extract(@anon, 2) extract(@anon, 1);
+ length: length(@anon);
+}
--- /dev/null
+@fat: 0;
+@cloudhead: "#000000";
+
+.nav {
+ filter: progid:DXImageTransform.Microsoft.Alpha(opacity = 20);
+ filter: progid:DXImageTransform.Microsoft.Alpha(opacity=@fat);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#333333", endColorstr=@cloudhead, GradientType=@fat);
+}
+.evalTest(@arg) {
+ filter: progid:DXImageTransform.Microsoft.Alpha(opacity=@arg);
+}
+.evalTest1 {
+ .evalTest(30);
+ .evalTest(5);
+}
\ No newline at end of file
--- /dev/null
+@import (inline) url("import/import-test-d.css") (min-width:600px);
+@import (inline, css) url("import/invalid-css.less");
\ No newline at end of file
--- /dev/null
+@my_theme: "test";
+
+@import "import/import-@{my_theme}-e.less";
+
+@import "import/import-@{in}@{terpolation}.less";
+
+@in: "in";
+@terpolation: "terpolation";
\ No newline at end of file
--- /dev/null
+@import "import/import-once-test-c";
+@import "import/import-once-test-c";
+@import "import/import-once-test-c.less";
+@import "import/deeper/import-once-test-a";
+@import (multiple) "import/import-test-f.less";
+@import (multiple) "import/import-test-f.less";
\ No newline at end of file
--- /dev/null
+@import (reference) url("import-once.less");
+@import (reference) url("css-3.less");
+@import (reference) url("media.less");
+/*
+ The media statement above is invalid (no selector)
+ We should ban invalid media queries with properties and no selector?
+*/
+@import (reference) url("import/import-reference.less");
+
+.b {
+ .z();
+}
+
+.zz();
+
+.visible:extend(.z all) {
+ extend: test;
+}
\ No newline at end of file
--- /dev/null
+@import url(http://fonts.googleapis.com/css?family=Open+Sans);
+
+@import url(/absolute/something.css) screen and (color) and (max-width: 600px);
+
+@var: 100px;
+@import url("//ha.com/file.css") (min-width:@var);
+
+#import-test {
+ .mixin;
+ width: 10px;
+ height: (@a + 10%);
+}
+@import "import/import-test-e" screen and (max-width: 600px);
+
+@import url("import/import-test-a.less");
+
+@import (less, multiple) "import/import-test-d.css" screen and (max-width: 601px);
+
+@import (multiple) "import/import-test-e" screen and (max-width: 602px);
+
+@import (less, multiple) url("import/import-test-d.css") screen and (max-width: 603px);
\ No newline at end of file
--- /dev/null
+@import "../import-once-test-c";
\ No newline at end of file
--- /dev/null
+@import "../css/background.css";
+@import "import-test-d.css";
+
+@import "imports/logo";
+@import "imports/font";
+
--- /dev/null
+@charset "ISO-8859-1";
\ No newline at end of file
--- /dev/null
+@import "import-@{in}@{terpolation}2.less";
\ No newline at end of file
--- /dev/null
+.a {
+ var: test;
+}
+
+@in: "redefined-does-nothing";
\ No newline at end of file
--- /dev/null
+
+@c: red;
+
+#import {
+ color: @c;
+}
--- /dev/null
+.z {
+ color: red;
+ .c {
+ color: green;
+ }
+}
+.only-with-visible,
+.z {
+ color: green;
+ &:hover {
+ color: green;
+ }
+ & {
+ color: green;
+ }
+ & + & {
+ color: green;
+ .sub {
+ color: green;
+ }
+ }
+}
+
+& {
+ .hidden {
+ hidden: true;
+ }
+}
+
+@media tv {
+ .hidden {
+ hidden: true;
+ }
+}
+
+/* comment is not output */
+
+.zz {
+ .y {
+ pulled-in: yes;
+ }
+ /* comment pulled in */
+}
\ No newline at end of file
--- /dev/null
+@import "import-test-b.less";
+@a: 20%;
+@import "urls.less";
\ No newline at end of file
--- /dev/null
+@import "import-test-c";
+
+@b: 100%;
+
+.mixin {
+ height: 10px;
+ color: @c;
+}
--- /dev/null
+
+@c: red;
+
+#import {
+ color: @c;
+}
--- /dev/null
+#css { color: yellow; }
--- /dev/null
+
+body { width: 100% }
--- /dev/null
+@import "import-test-e";
+
+.test-f {
+ height: 10px;
+}
--- /dev/null
+@font-face {
+ font-family: xecret;
+ src: url('../assets/xecret.ttf');
+}
+
+#secret {
+ font-family: xecret, sans-serif;
+}
--- /dev/null
+#logo {
+ width: 100px;
+ height: 100px;
+ background: url('../assets/logo.png');
+}
--- /dev/null
+this isn't very valid CSS.
\ No newline at end of file
--- /dev/null
+// empty file showing that it loads from the relative path first
--- /dev/null
+.eval {
+ js: `42`;
+ js: `1 + 1`;
+ js: `"hello world"`;
+ js: `[1, 2, 3]`;
+ title: `typeof process.title`;
+ ternary: `(1 + 1 == 2 ? true : false)`;
+ multiline: `(function(){var x = 1 + 1;
+ return x})()`;
+}
+.scope {
+ @foo: 42;
+ var: `parseInt(this.foo.toJS())`;
+ escaped: ~`2 + 5 + 'px'`;
+}
+.vars {
+ @var: `4 + 4`;
+ width: @var;
+}
+.escape-interpol {
+ @world: "world";
+ width: ~`"hello" + " " + @{world}`;
+}
+.arrays {
+ @ary: 1, 2, 3;
+ @ary2: 1 2 3;
+ ary: `@{ary}.join(', ')`;
+ ary1: `@{ary2}.join(', ')`;
+}
--- /dev/null
+@var: @a;
+@a: 100%;
+
+.lazy-eval {
+ width: @var;
+}
--- /dev/null
+@media (-o-min-device-pixel-ratio: 2/1) {
+ .test-math-and-units {
+ font: ignores 0/0 rules;
+ test-division: 4 / 2 + 5em;
+ simple: 1px + 1px;
+ }
+}
\ No newline at end of file
--- /dev/null
+
+// For now, variables can't be declared inside @media blocks.
+
+@var: 42;
+
+@media print {
+ .class {
+ color: blue;
+ .sub {
+ width: @var;
+ }
+ }
+ .top, header > h1 {
+ color: (#222 * 2);
+ }
+}
+
+@media screen {
+ @base: 8;
+ body { max-width: (@base * 60); }
+}
+
+@ratio_large: 16;
+@ratio_small: 9;
+
+@media all and (device-aspect-ratio: @ratio_large / @ratio_small) {
+ body { max-width: 800px; }
+}
+
+@media all and (orientation:portrait) {
+ aside { float: none; }
+}
+
+@media handheld and (min-width: @var), screen and (min-width: 20em) {
+ body {
+ max-width: 480px;
+ }
+}
+
+body {
+ @media print {
+ padding: 20px;
+
+ header {
+ background-color: red;
+ }
+
+ @media (orientation:landscape) {
+ margin-left: 20px;
+ }
+ }
+}
+
+@media screen {
+ .sidebar {
+ width: 300px;
+ @media (orientation: landscape) {
+ width: 500px;
+ }
+ }
+}
+
+@media a {
+ .first {
+ @media b {
+ .second {
+ .third {
+ width: 300px;
+ @media c {
+ width: 500px;
+ }
+ }
+ .fourth {
+ width: 3;
+ }
+ }
+ }
+ }
+}
+
+body {
+ @media a, b and c {
+ width: 95%;
+
+ @media x, y {
+ width: 100%;
+ }
+ }
+}
+
+.mediaMixin(@fallback: 200px) {
+ background: black;
+
+ @media handheld {
+ background: white;
+
+ @media (max-width: @fallback) {
+ background: red;
+ }
+ }
+}
+
+.a {
+ .mediaMixin(100px);
+}
+
+.b {
+ .mediaMixin();
+}
+@smartphone: ~"only screen and (max-width: 200px)";
+@media @smartphone {
+ body {
+ width: 480px;
+ }
+}
+
+@media print {
+ @page :left {
+ margin: 0.5cm;
+ }
+ @page :right {
+ margin: 0.5cm;
+ }
+ @page Test:first {
+ margin: 1cm;
+ }
+ @page :first {
+ size: 8.5in 11in;
+ @top-left {
+ margin: 1cm;
+ }
+ @top-left-corner {
+ margin: 1cm;
+ }
+ @top-center {
+ margin: 1cm;
+ }
+ @top-right {
+ margin: 1cm;
+ }
+ @top-right-corner {
+ margin: 1cm;
+ }
+ @bottom-left {
+ margin: 1cm;
+ }
+ @bottom-left-corner {
+ margin: 1cm;
+ }
+ @bottom-center {
+ margin: 1cm;
+ }
+ @bottom-right {
+ margin: 1cm;
+ }
+ @bottom-right-corner {
+ margin: 1cm;
+ }
+ @left-top {
+ margin: 1cm;
+ }
+ @left-middle {
+ margin: 1cm;
+ }
+ @left-bottom {
+ margin: 1cm;
+ }
+ @right-top {
+ margin: 1cm;
+ }
+ @right-middle {
+ content: "Page " counter(page);
+ }
+ @right-bottom {
+ margin: 1cm;
+ }
+ }
+}
+
+@media (-webkit-min-device-pixel-ratio: 2), (min--moz-device-pixel-ratio: 2), (-o-min-device-pixel-ratio: 2/1), (min-resolution: 2dppx), (min-resolution: 128dpcm) {
+ .b {
+ background: red;
+ }
+}
+
+.bg() {
+ background: red;
+
+ @media (max-width: 500px) {
+ background: green;
+ }
+}
+
+body {
+ .bg();
+}
+
+@bpMedium: 1000px;
+@media (max-width: @bpMedium) {
+ body {
+ .bg();
+ background: blue;
+ }
+}
+
+@media (max-width: 1200px) {
+ /* a comment */
+
+ @media (max-width: 900px) {
+ body { font-size: 11px; }
+ }
+}
+
+.nav-justified {
+ @media (min-width: 480px) {
+ > li {
+ display: table-cell;
+ }
+ }
+}
+
+.menu
+{
+ @media (min-width: 768px) {
+ .nav-justified();
+ }
+}
+@all: ~"all";
+@tv: ~"tv";
+@media @all and @tv {
+ .all-and-tv-variables {
+ var: all-and-tv;
+ }
+}
\ No newline at end of file
--- /dev/null
+.first-transform() {
+ transform+: rotate(90deg), skew(30deg);
+}
+.second-transform() {
+ transform+: scale(2,4);
+}
+.third-transform() {
+ transform: scaleX(45deg);
+}
+.fourth-transform() {
+ transform+: scaleX(45deg);
+}
+.fifth-transform() {
+ transform+: scale(2,4) !important;
+}
+.first-background() {
+ background+: url(data://img1.png);
+}
+.second-background() {
+ background+: url(data://img2.png);
+}
+
+.test1 {
+ // Can merge values
+ .first-transform();
+ .second-transform();
+}
+.test2 {
+ // Wont merge values without +: merge directive, for backwards compatibility with css
+ .first-transform();
+ .third-transform();
+}
+.test3 {
+ // Wont merge values from two sources with different properties
+ .fourth-transform();
+ .first-background();
+}
+.test4 {
+ // Wont merge values from sources that merked as !important, for backwards compatibility with css
+ .first-transform();
+ .fifth-transform();
+}
+.test5 {
+ // Wont merge values from mixins that merked as !important, for backwards compatibility with css
+ .first-transform();
+ .second-transform() !important;
+}
+.test6 {
+ // Ignores !merge if no peers found
+ .second-transform();
+}
\ No newline at end of file
--- /dev/null
+.mixin (@a: 1px, @b: 50%) {
+ width: (@a * 5);
+ height: (@b - 1%);
+}
+
+.mixina (@style, @width, @color: black) {
+ border: @width @style @color;
+}
+
+.mixiny
+(@a: 0, @b: 0) {
+ margin: @a;
+ padding: @b;
+}
+
+.hidden() {
+ color: transparent; // asd
+}
+
+#hidden {
+ .hidden;
+}
+
+#hidden1 {
+ .hidden();
+}
+
+.two-args {
+ color: blue;
+ .mixin(2px, 100%);
+ .mixina(dotted, 2px);
+}
+
+.one-arg {
+ .mixin(3px);
+}
+
+.no-parens {
+ .mixin;
+}
+
+.no-args {
+ .mixin();
+}
+
+.var-args {
+ @var: 9;
+ .mixin(@var, (@var * 2));
+}
+
+.multi-mix {
+ .mixin(2px, 30%);
+ .mixiny(4, 5);
+}
+
+.maxa(@arg1: 10, @arg2: #f00) {
+ padding: (@arg1 * 2px);
+ color: @arg2;
+}
+
+body {
+ .maxa(15);
+}
+
+@glob: 5;
+.global-mixin(@a:2) {
+ width: (@glob + @a);
+}
+
+.scope-mix {
+ .global-mixin(3);
+}
+
+.nested-ruleset (@width: 200px) {
+ width: @width;
+ .column { margin: @width; }
+}
+.content {
+ .nested-ruleset(600px);
+}
+
+//
+
+.same-var-name2(@radius) {
+ radius: @radius;
+}
+.same-var-name(@radius) {
+ .same-var-name2(@radius);
+}
+#same-var-name {
+ .same-var-name(5px);
+}
+
+//
+
+.var-inside () {
+ @var: 10px;
+ width: @var;
+}
+#var-inside { .var-inside; }
+
+.mixin-arguments (@width: 0px, ...) {
+ border: @arguments;
+ width: @width;
+}
+
+.arguments {
+ .mixin-arguments(1px, solid, black);
+}
+.arguments2 {
+ .mixin-arguments();
+}
+.arguments3 {
+ .mixin-arguments;
+}
+
+.mixin-arguments2 (@width, @rest...) {
+ border: @arguments;
+ rest: @rest;
+ width: @width;
+}
+.arguments4 {
+ .mixin-arguments2(0, 1, 2, 3, 4);
+}
+
+// Edge cases
+
+.edge-case {
+ .mixin-arguments("{");
+}
+
+// Division vs. Literal Slash
+.border-radius(@r: 2px/5px) {
+ border-radius: @r;
+}
+.slash-vs-math {
+ .border-radius();
+ .border-radius(5px/10px);
+ .border-radius((3px * 2));
+}
+// semi-colon vs comma for delimiting
+
+.mixin-takes-one(@a) {
+ one: @a;
+}
+
+.mixin-takes-two(@a; @b) {
+ one: @a;
+ two: @b;
+}
+
+.comma-vs-semi-colon {
+ .mixin-takes-two(@a : a; @b : b, c);
+ .mixin-takes-two(@a : d, e; @b : f);
+ .mixin-takes-one(@a: g);
+ .mixin-takes-one(@a : h;);
+ .mixin-takes-one(i);
+ .mixin-takes-one(j;);
+ .mixin-takes-two(k, l);
+ .mixin-takes-one(m, n;);
+ .mixin-takes-two(o, p; q);
+ .mixin-takes-two(r, s; t;);
+}
+
+.mixin-conflict(@a:defA, @b:defB, @c:defC) {
+ three: @a, @b, @c;
+}
+
+.mixin-conflict(@a:defA, @b:defB, @c:defC, @d:defD) {
+ four: @a, @b, @c, @d;
+}
+
+#named-conflict {
+ .mixin-conflict(11, 12, 13, @a:a);
+ .mixin-conflict(@a:a, 21, 22, 23);
+}
+@a: 3px;
+.mixin-default-arg(@a: 1px, @b: @a, @c: @b) {
+ defaults: 1px 1px 1px;
+ defaults: 2px 2px 2px;
+}
+
+.test-mixin-default-arg {
+ .mixin-default-arg();
+ .mixin-default-arg(2px);
+}
+
+.mixin-comma-default1(@color; @padding; @margin: 2, 2, 2, 2) {
+ margin: @margin;
+}
+.selector {
+ .mixin-comma-default1(#33acfe; 4);
+}
+.mixin-comma-default2(@margin: 2, 2, 2, 2;) {
+ margin: @margin;
+}
+.selector2 {
+ .mixin-comma-default2();
+}
+.mixin-comma-default3(@margin: 2, 2, 2, 2) {
+ margin: @margin;
+}
+.selector3 {
+ .mixin-comma-default3(4,2,2,2);
+}
+
+.test-calling-one-arg-mixin(@a) {
+}
+
+.test-calling-one-arg-mixin(@a, @b, @rest...) {
+}
+
+div {
+ .test-calling-one-arg-mixin(1);
+}
\ No newline at end of file
--- /dev/null
+.scope {
+ @var: 99px;
+ .mixin () {
+ width: @var;
+ }
+}
+
+.class {
+ .scope > .mixin;
+}
+
+.overwrite {
+ @var: 0px;
+ .scope > .mixin;
+}
+
+.nested {
+ @var: 5px;
+ .mixin () {
+ width: @var;
+ }
+ .class {
+ @var: 10px;
+ .mixin;
+ }
+}
--- /dev/null
+
+// Stacking, functions..
+
+.light (@a) when (lightness(@a) > 50%) {
+ color: white;
+}
+.light (@a) when (lightness(@a) < 50%) {
+ color: black;
+}
+.light (@a) {
+ margin: 1px;
+}
+
+.light1 { .light(#ddd) }
+.light2 { .light(#444) }
+
+// Arguments against each other
+
+.max (@a, @b) when (@a > @b) {
+ width: @a;
+}
+.max (@a, @b) when (@a < @b) {
+ width: @b;
+}
+
+.max1 { .max(3, 6) }
+.max2 { .max(8, 1) }
+
+// Globals inside guards
+
+@g: auto;
+
+.glob (@a) when (@a = @g) {
+ margin: @a @g;
+}
+.glob1 { .glob(auto) }
+
+// Other operators
+
+.ops (@a) when (@a >= 0) {
+ height: gt-or-eq;
+}
+.ops (@a) when (@a =< 0) {
+ height: lt-or-eq;
+}
+.ops (@a) when (@a <= 0) {
+ height: lt-or-eq-alias;
+}
+.ops (@a) when not(@a = 0) {
+ height: not-eq;
+}
+.ops1 { .ops(0) }
+.ops2 { .ops(1) }
+.ops3 { .ops(-1) }
+
+// Scope and default values
+
+@a: auto;
+
+.default (@a: inherit) when (@a = inherit) {
+ content: default;
+}
+.default1 { .default }
+
+// true & false keywords
+.test (@a) when (@a) {
+ content: "true.";
+}
+.test (@a) when not (@a) {
+ content: "false.";
+}
+
+.test1 { .test(true) }
+.test2 { .test(false) }
+.test3 { .test(1) }
+.test4 { .test(boo) }
+.test5 { .test("true") }
+
+// Boolean expressions
+
+.bool () when (true) and (false) { content: true and false } // FALSE
+.bool () when (true) and (true) { content: true and true } // TRUE
+.bool () when (true) { content: true } // TRUE
+.bool () when (false) and (false) { content: true } // FALSE
+.bool () when (false), (true) { content: false, true } // TRUE
+.bool () when (false) and (true) and (true), (true) { content: false and true and true, true } // TRUE
+.bool () when (true) and (true) and (false), (false) { content: true and true and false, false } // FALSE
+.bool () when (false), (true) and (true) { content: false, true and true } // TRUE
+.bool () when (false), (false), (true) { content: false, false, true } // TRUE
+.bool () when (false), (false) and (true), (false) { content: false, false and true, false } // FALSE
+.bool () when (false), (true) and (true) and (true), (false) { content: false, true and true and true, false } // TRUE
+.bool () when not (false) { content: not false }
+.bool () when not (true) and not (false) { content: not true and not false }
+.bool () when not (true) and not (true) { content: not true and not true }
+.bool () when not (false) and (false), not (false) { content: not false and false, not false }
+
+.bool1 { .bool }
+
+.equality-unit-test(@num) when (@num = 1%) {
+ test: fail;
+}
+.equality-unit-test(@num) when (@num = 2) {
+ test: pass;
+}
+.equality-units {
+ .equality-unit-test(1px);
+ .equality-unit-test(2px);
+}
+
+.colorguard(@col) when (@col = red) { content: is @col; }
+.colorguard(@col) when not (blue = @col) { content: is not blue its @col; }
+.colorguard(@col) {}
+.colorguardtest {
+ .colorguard(red);
+ .colorguard(blue);
+ .colorguard(purple);
+}
+
+.stringguard(@str) when (@str = "theme1") { content: is theme1; }
+.stringguard(@str) when not ("theme2" = @str) { content: is not theme2; }
+.stringguard(@str) when (~"theme1" = @str) { content: is theme1 no quotes; }
+.stringguard(@str) {}
+.stringguardtest {
+ .stringguard("theme1");
+ .stringguard("theme2");
+ .stringguard(theme1);
+}
+
+.mixin(...) {
+ catch:all;
+}
+.mixin(@var) when (@var=4) {
+ declare: 4;
+}
+.mixin(@var) when (@var=4px) {
+ declare: 4px;
+}
+#tryNumberPx {
+ .mixin(4px);
+}
+
+.lock-mixin(@a) {
+ .inner-locked-mixin(@x: @a) when (@a = 1) {
+ a: @a;
+ x: @x;
+ }
+}
+.call-lock-mixin {
+ .lock-mixin(1);
+ .call-inner-lock-mixin {
+ .inner-locked-mixin();
+ }
+}
\ No newline at end of file
--- /dev/null
+.submixin(@a) {
+ border-width: @a;
+}
+.mixin (9) {
+ border: 9 !important;
+}
+.mixin (@a: 0) {
+ border: @a;
+ boxer: @a;
+ .inner {
+ test: @a;
+ }
+ // comment
+ .submixin(@a);
+}
+
+.class {
+ .mixin(1);
+ .mixin(2) !important;
+ .mixin(3);
+ .mixin(4) !important;
+ .mixin(5);
+ .mixin !important;
+ .mixin(9);
+}
--- /dev/null
+.mixin (@a: 1px, @b: 50%) {
+ width: (@a * 5);
+ height: (@b - 1%);
+ args: @arguments;
+}
+.mixin (@a: 1px, @b: 50%) when (@b > 75%){
+ text-align: center;
+}
+
+.named-arg {
+ color: blue;
+ .mixin(@b: 100%);
+}
+
+.class {
+ @var: 20%;
+ .mixin(@b: @var);
+}
+
+.all-args-wrong-args {
+ .mixin(@b: 10%, @a: 2px);
+}
+
+.mixin2 (@a: 1px, @b: 50%, @c: 50) {
+ width: (@a * 5);
+ height: (@b - 1%);
+ color: (#000000 + @c);
+}
+
+.named-args2 {
+ .mixin2(3px, @c: 100);
+}
+
+.named-args3 {
+ .mixin2(@b: 30%, @c: #123456);
+}
\ No newline at end of file
--- /dev/null
+.mix-inner (@var) {
+ border-width: @var;
+}
+
+.mix (@a: 10) {
+ .inner {
+ height: (@a * 10);
+
+ .innest {
+ width: @a;
+ .mix-inner((@a * 2));
+ }
+ }
+}
+
+.class {
+ .mix(30);
+}
+
+.class2 {
+ .mix(60);
+}
--- /dev/null
+.mixin (...) {
+ variadic: true;
+}
+.mixin () {
+ zero: 0;
+}
+.mixin (@a: 1px) {
+ one: 1;
+}
+.mixin (@a) {
+ one-req: 1;
+}
+.mixin (@a: 1px, @b: 2px) {
+ two: 2;
+}
+
+.mixin (@a, @b, @c) {
+ three-req: 3;
+}
+
+.mixin (@a: 1px, @b: 2px, @c: 3px) {
+ three: 3;
+}
+
+.zero {
+ .mixin();
+}
+
+.one {
+ .mixin(1);
+}
+
+.two {
+ .mixin(1, 2);
+}
+
+.three {
+ .mixin(1, 2, 3);
+}
+
+//
+
+.mixout ('left') {
+ left: 1;
+}
+
+.mixout ('right') {
+ right: 1;
+}
+
+.left {
+ .mixout('left');
+}
+.right {
+ .mixout('right');
+}
+
+//
+
+.border (@side, @width) {
+ color: black;
+ .border-side(@side, @width);
+}
+.border-side (left, @w) {
+ border-left: @w;
+}
+.border-side (right, @w) {
+ border-right: @w;
+}
+
+.border-right {
+ .border(right, 4px);
+}
+.border-left {
+ .border(left, 4px);
+}
+
+//
+
+
+.border-radius (@r) {
+ both: (@r * 10);
+}
+.border-radius (@r, left) {
+ left: @r;
+}
+.border-radius (@r, right) {
+ right: @r;
+}
+
+.only-right {
+ .border-radius(33, right);
+}
+.only-left {
+ .border-radius(33, left);
+}
+.left-right {
+ .border-radius(33);
+}
--- /dev/null
+.mixin { border: 1px solid black; }
+.mixout { border-color: orange; }
+.borders { border-style: dashed; }
+
+#namespace {
+ .borders {
+ border-style: dotted;
+ }
+ .biohazard {
+ content: "death";
+ .man {
+ color: transparent;
+ }
+ }
+}
+#theme {
+ > .mixin {
+ background-color: grey;
+ }
+}
+#container {
+ color: black;
+ .mixin;
+ .mixout;
+ #theme > .mixin;
+}
+
+#header {
+ .milk {
+ color: white;
+ .mixin;
+ #theme > .mixin;
+ }
+ #cookie {
+ .chips {
+ #namespace .borders;
+ .calories {
+ #container;
+ }
+ }
+ .borders;
+ }
+}
+.secure-zone { #namespace .biohazard .man; }
+.direct {
+ #namespace > .borders;
+}
+
+.bo, .bar {
+ width: 100%;
+}
+.bo {
+ border: 1px;
+}
+.ar.bo.ca {
+ color: black;
+}
+.jo.ki {
+ background: none;
+}
+.amp {
+ &.support {
+ color: orange;
+ .higher {
+ top: 0px;
+ }
+ &.deeper {
+ height: auto;
+ }
+ }
+}
+.extended {
+ .bo;
+ .jo.ki;
+ .amp.support;
+ .amp.support.higher;
+ .amp.support.deeper;
+}
+.do .re .mi .fa {
+ .sol .la {
+ .si {
+ color: cyan;
+ }
+ }
+}
+.mutli-selector-parents {
+ .do.re.mi.fa.sol.la.si;
+}
+.foo .bar {
+ .bar;
+}
+.has_parents() {
+ & .underParents {
+ color: red;
+ }
+}
+.has_parents();
+.parent {
+ .has_parents();
+}
+.margin_between(@above, @below) {
+ * + & { margin-top: @above; }
+ legend + & { margin-top: 0; }
+ & + * { margin-top: @below; }
+}
+h1 { .margin_between(25px, 10px); }
+h2 { .margin_between(20px, 8px); }
+h3 { .margin_between(15px, 5px); }
+
+.mixin_def(@url, @position){
+ background-image: @url;
+ background-position: @position;
+}
+.error{
+ @s: "/";
+ .mixin_def( "@{s}a.png", center center);
+}
+.recursion() {
+ color: black;
+}
+.test-rec {
+ .recursion {
+ .recursion();
+ }
+}
+.paddingFloat(@padding) { padding-left: @padding; }
+
+.button {
+ .paddingFloat(((10px + 12) * 2));
+
+ &.large { .paddingFloat(((10em * 2) * 2)); }
+}
+.clearfix() {
+ // ...
+}
+.clearfix {
+ .clearfix();
+}
+.foo {
+ .clearfix();
+}
\ No newline at end of file
--- /dev/null
+.a {
+ a: `1 + 1`;
+}
\ No newline at end of file
--- /dev/null
+SyntaxError: You are using JavaScript, which has been disabled. in {path}no-js-errors.less on line 2, column 6:
+1 .a {
+2 a: `1 + 1`;
+3 }
--- /dev/null
+.mixin() {
+}
\ No newline at end of file
--- /dev/null
+#operations {
+ color: (#110000 + #000011 + #001100); // #111111
+ height: (10px / 2px + 6px - 1px * 2); // 9px
+ width: (2 * 4 - 5em); // 3em
+ .spacing {
+ height: (10px / 2px+6px-1px*2);
+ width: (2 * 4-5em);
+ }
+ substraction: (20 - 10 - 5 - 5); // 0
+ division: (20 / 5 / 4); // 1
+}
+
+@x: 4;
+@y: 12em;
+
+.with-variables {
+ height: (@x + @y); // 16em
+ width: (12 + @y); // 24em
+ size: (5cm - @x); // 1cm
+}
+
+.with-functions {
+ color: (rgb(200, 200, 200) / 2);
+ color: (2 * hsl(0, 50%, 50%));
+ color: (rgb(10, 10, 10) + hsl(0, 50%, 50%));
+}
+
+@z: -2;
+
+.negative {
+ height: (2px + @z); // 0px
+ width: (2px - @z); // 4px
+}
+
+.shorthands {
+ padding: -1px 2px 0 -4px; //
+}
+
+.rem-dimensions {
+ font-size: (20rem / 5 + 1.5rem); // 5.5rem
+}
+
+.colors {
+ color: #123; // #112233
+ border-color: (#234 + #111111); // #334455
+ background-color: (#222222 - #fff); // #000000
+ .other {
+ color: (2 * #111); // #222222
+ border-color: (#333333 / 3 + #111); // #222222
+ }
+}
+
+.negations {
+ @var: 4px;
+ variable: (-@var); // 4
+ variable1: (-@var + @var); // 0
+ variable2: (@var + -@var); // 0
+ variable3: (@var - -@var); // 8
+ variable4: (-@var - -@var); // 0
+ paren: (-(@var)); // -4px
+ paren2: (-(2 + 2) * -@var); // 16
+}
--- /dev/null
+.parens {
+ @var: 1px;
+ border: (@var * 2) solid black;
+ margin: (@var * 1) (@var + 2) (4 * 4) 3;
+ width: (6 * 6);
+ padding: 2px (6 * 6px);
+}
+
+.more-parens {
+ @var: (2 * 2);
+ padding: (2 * @var) 4 4 (@var * 1px);
+ width-all: ((@var * @var) * 6);
+ width-first: ((@var * @var)) * 6;
+ width-keep: (@var * @var) * 6;
+ height-keep: (7 * 7) + (8 * 8);
+ height-all: ((7 * 7) + (8 * 8));
+ height-parts: ((7 * 7)) + ((8 * 8));
+ margin-keep: (4 * (5 + 5) / 2) - (@var * 2);
+ margin-parts: ((4 * (5 + 5) / 2)) - ((@var * 2));
+ margin-all: ((4 * (5 + 5) / 2) + (-(@var * 2)));
+ border-radius-keep: 4px * (1 + 1) / @var + 3px;
+ border-radius-parts: ((4px * (1 + 1))) / ((@var + 3px));
+ border-radius-all: (4px * (1 + 1) / @var + 3px);
+ //margin: (6 * 6)px;
+}
+
+.negative {
+ @var: 1;
+ neg-var: -@var; // -1 ?
+ neg-var-paren: -(@var); // -(1) ?
+}
+
+.nested-parens {
+ width: 2 * (4 * (2 + (1 + 6))) - 1;
+ height: ((2 + 3) * (2 + 3) / (9 - 4)) + 1;
+}
+
+.mixed-units {
+ margin: 2px 4em 1 5pc;
+ padding: (2px + 4px) 1em 2px 2;
+}
--- /dev/null
+#first > .one {
+ > #second .two > #deux {
+ width: 50%;
+ #third {
+ &:focus {
+ color: black;
+ #fifth {
+ > #sixth {
+ .seventh #eighth {
+ + #ninth {
+ color: purple;
+ }
+ }
+ }
+ }
+ }
+ height: 100%;
+ }
+ #fourth, #five, #six {
+ color: #110000;
+ .seven, .eight > #nine {
+ border: 1px solid black;
+ }
+ #ten {
+ color: red;
+ }
+ }
+ }
+ font-size: 2em;
+}
--- /dev/null
+@x: red;
+@x: blue;
+@z: transparent;
+@mix: none;
+
+.mixin {
+ @mix: #989;
+}
+@mix: blue;
+.tiny-scope {
+ color: @mix; // #989
+ .mixin;
+}
+
+.scope1 {
+ @y: orange;
+ @z: black;
+ color: @x; // blue
+ border-color: @z; // black
+ .hidden {
+ @x: #131313;
+ }
+ .scope2 {
+ @y: red;
+ color: @x; // blue
+ .scope3 {
+ @local: white;
+ color: @y; // red
+ border-color: @z; // black
+ background-color: @local; // white
+ }
+ }
+}
+
+#namespace {
+ .scoped_mixin() {
+ @local-will-be-made-global: green;
+ .scope {
+ scoped-val: @local-will-be-made-global;
+ }
+ }
+}
+
+#namespace > .scoped_mixin();
+
+.setHeight(@h) { @height: 1024px; }
+.useHeightInMixinCall(@h) { .useHeightInMixinCall { mixin-height: @h; } }
+@mainHeight: 50%;
+.setHeight(@mainHeight);
+.heightIsSet { height: @height; }
+.useHeightInMixinCall(@height);
+
+.importRuleset() {
+ .imported {
+ exists: true;
+ }
+}
+.importRuleset();
+.testImported {
+ .imported;
+}
+
+@parameterDefault: 'top level';
+@anotherVariable: 'top level';
+//mixin uses top-level variables
+.mixinNoParam(@parameter: @parameterDefault) when (@parameter = 'top level') {
+ default: @parameter;
+ scope: @anotherVariable;
+ sub-scope-only: @subScopeOnly;
+}
+
+#allAreUsedHere {
+ //redefine top-level variables in different scope
+ @parameterDefault: 'inside';
+ @anotherVariable: 'inside';
+ @subScopeOnly: 'inside';
+ //use the mixin
+ .mixinNoParam();
+}
\ No newline at end of file
--- /dev/null
+h1, h2, h3 {
+ a, p {
+ &:hover {
+ color: red;
+ }
+ }
+}
+
+#all { color: blue; }
+#the { color: blue; }
+#same { color: blue; }
+
+ul, li, div, q, blockquote, textarea {
+ margin: 0;
+}
+
+td {
+ margin: 0;
+ padding: 0;
+}
+
+td, input {
+ line-height: 1em;
+}
+
+a {
+ color: red;
+
+ &:hover { color: blue; }
+
+ div & { color: green; }
+
+ p & span { color: yellow; }
+}
+
+.foo {
+ .bar, .baz {
+ & .qux {
+ display: block;
+ }
+ .qux & {
+ display: inline;
+ }
+ .qux& {
+ display: inline-block;
+ }
+ .qux & .biz {
+ display: none;
+ }
+ }
+}
+
+.b {
+ &.c {
+ .a& {
+ color: red;
+ }
+ }
+}
+
+.b {
+ .c & {
+ &.a {
+ color: red;
+ }
+ }
+}
+
+.p {
+ .foo &.bar {
+ color: red;
+ }
+}
+
+.p {
+ .foo&.bar {
+ color: red;
+ }
+}
+
+.foo {
+ .foo + & {
+ background: amber;
+ }
+ & + & {
+ background: amber;
+ }
+}
+
+.foo, .bar {
+ & + & {
+ background: amber;
+ }
+}
+
+.foo, .bar {
+ a, b {
+ & > & {
+ background: amber;
+ }
+ }
+}
+
+.other ::fnord { color: red }
+.other::fnord { color: red }
+.other {
+ ::bnord {color: red }
+ &::bnord {color: red }
+}
+// selector interpolation
+@theme: blood;
+@selector: ~".@{theme}";
+@{selector} {
+ color:red;
+}
+@{selector}red {
+ color: green;
+}
+.red {
+ #@{theme}.@{theme}&.black {
+ color:black;
+ }
+}
+@num: 3;
+:nth-child(@{num}) {
+ selector: interpolated;
+}
+.test {
+ &:nth-child(odd):not(:nth-child(3)) {
+ color: #ff0000;
+ }
+}
+[prop],
+[prop=10%],
+[prop="value@{num}"],
+[prop*="val@{num}"],
+[|prop~="val@{num}"],
+[*|prop$="val@{num}"],
+[ns|prop^="val@{num}"],
+[@{num}^="val@{num}"],
+[@{num}=@{num}],
+[@{num}] {
+ attributes: yes;
+}
\ No newline at end of file
--- /dev/null
+@var: black;
+
+.a() {
+ color: red;
+}
+
+.b {
+ color: green;
+ .a();
+ color: blue;
+ background: @var;
+}
+
+.a, .b {
+ background: green;
+ .c, .d {
+ background: gray;
+ & + & {
+ color: red;
+ }
+ }
+}
+
+.extend:extend(.a all) {
+ color: pink;
+}
+@import (inline) "imported.css";
\ No newline at end of file
--- /dev/null
+/*comments*/
+.unused-css {
+ color: white;
+}
+.imported {
+ color: black;
+}
\ No newline at end of file
--- /dev/null
+@font-face {
+ src: local(Futura-Medium),
+ url(fonts.svg#MyGeometricModern) format("svg");
+}
+#shorthands {
+ background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
+}
+#misc {
+ background-image: url(images/image.jpg);
+}
+#data-uri {
+ background: url(data:image/png;charset=utf-8;base64,
+ kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
+ k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
+ kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
+ background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
+ background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
+}
+
+#svg-data-uri {
+ background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
+}
+
+.comma-delimited {
+ background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);
+}
+.values {
+ @a: 'Trebuchet';
+ url: url(@a);
+}
+
+@import "../import/import-and-relative-paths-test";
--- /dev/null
+#strings {
+ background-image: url("http://son-of-a-banana.com");
+ quotes: "~" "~";
+ content: "#*%:&^,)!.(~*})";
+ empty: "";
+ brackets: "{" "}";
+ escapes: "\"hello\" \\world";
+ escapes2: "\"llo";
+}
+#comments {
+ content: "/* hello */ // not-so-secret";
+}
+#single-quote {
+ quotes: "'" "'";
+ content: '""#!&""';
+ empty: '';
+ semi-colon: ';';
+}
+#escaped {
+ filter: ~"DX.Transform.MS.BS.filter(opacity=50)";
+}
+#one-line { image: url(http://tooks.com) }
+#crazy { image: url(http://), "}", url("http://}") }
+#interpolation {
+ @var: '/dev';
+ url: "http://lesscss.org@{var}/image.jpg";
+
+ @var2: 256;
+ url2: "http://lesscss.org/image-@{var2}.jpg";
+
+ @var3: #456;
+ url3: "http://lesscss.org@{var3}";
+
+ @var4: hello;
+ url4: "http://lesscss.org/@{var4}";
+
+ @var5: 54.4px;
+ url5: "http://lesscss.org/@{var5}";
+}
+
+// multiple calls with string interpolation
+
+.mix-mul (@a: green) {
+ color: ~"@{a}";
+}
+.mix-mul-class {
+ .mix-mul(blue);
+ .mix-mul(red);
+ .mix-mul(black);
+ .mix-mul(orange);
+}
+
+@test: Arial, Verdana, San-Serif;
+.watermark {
+ @family: ~"Univers, @{test}";
+ family: @family;
+}
--- /dev/null
+@font-face {
+ src: local(Futura-Medium),
+ url(fonts.svg#MyGeometricModern) format("svg");
+}
+#shorthands {
+ background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
+ background: url("img.jpg") center / 100px;
+ background: #fff url(image.png) center / 1px 100px repeat-x scroll content-box padding-box;
+}
+#misc {
+ background-image: url(images/image.jpg);
+}
+#data-uri {
+ background: url(data:image/png;charset=utf-8;base64,
+ kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
+ k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
+ kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
+ background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
+ background-image: url(http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700);
+ background-image: url("http://fonts.googleapis.com/css?family=\"Rokkitt\":\(400\),700");
+}
+
+#svg-data-uri {
+ background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
+}
+
+.comma-delimited {
+ background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);
+}
+.values {
+ @a: 'Trebuchet';
+ url: url(@a);
+}
+
+@import "import/import-and-relative-paths-test";
+
+#data-uri {
+ uri: data-uri('image/jpeg;base64', '../data/image.jpg');
+}
+
+#data-uri-guess {
+ uri: data-uri('../data/image.jpg');
+}
+
+#data-uri-ascii {
+ uri-1: data-uri('text/html', '../data/page.html');
+ uri-2: data-uri('../data/page.html');
+}
+
+#data-uri-toobig {
+ uri: data-uri('../data/data-uri-fail.png');
+}
+.add_an_import(@file_to_import) {
+@import "@{file_to_import}";
+}
+
+.add_an_import("file.css");
+
+#svg-functions {
+ background-image: svg-gradient(to bottom, black, white);
+ background-image: svg-gradient(to bottom, black, orange 3%, white);
+ @green_5: green 5%;
+ @orange_percentage: 3%;
+ @orange_color: orange;
+ background-image: svg-gradient(to bottom, (mix(black, white) + #444) 1%, @orange_color @orange_percentage, ((@green_5)), white 95%);
+}
--- /dev/null
+@a: 2;
+@x: (@a * @a);
+@y: (@x + 1);
+@z: (@x * 2 + @y);
+@var: -1;
+
+.variables {
+ width: (@z + 1cm); // 14cm
+}
+
+@b: @a * 10;
+@c: #888;
+
+@fonts: "Trebuchet MS", Verdana, sans-serif;
+@f: @fonts;
+
+@quotes: "~" "~";
+@q: @quotes;
+@onePixel: 1px;
+
+.variables {
+ height: (@b + @x + 0px); // 24px
+ color: @c;
+ font-family: @f;
+ quotes: @q;
+}
+
+.redef {
+ @var: 0;
+ .inition {
+ @var: 4;
+ @var: 2;
+ three: @var;
+ @var: 3;
+ }
+ zero: @var;
+}
+
+.values {
+ minus-one: @var;
+ @a: 'Trebuchet';
+ @multi: 'A', B, C;
+ font-family: @a, @a, @a;
+ color: @c !important;
+ multi: something @multi, @a;
+}
+
+.variable-names {
+ @var: 'hello';
+ @name: 'var';
+ name: @@name;
+}
+
+.alpha {
+ @var: 42;
+ filter: alpha(opacity=@var);
+}
+
+.polluteMixin() {
+ @a: 'pollution';
+}
+.testPollution {
+ @a: 'no-pollution';
+ a: @a;
+ .polluteMixin();
+ a: @a;
+}
+
+.units {
+ width: @onePixel;
+ same-unit-as-previously: (@onePixel / @onePixel);
+ square-pixel-divided: (@onePixel * @onePixel / @onePixel);
+ odd-unit: unit((@onePixel * 4em / 2cm));
+ percentage: (10 * 50%);
+ pixels: (50px * 10);
+ conversion-metric-a: (20mm + 1cm);
+ conversion-metric-b: (1cm + 20mm);
+ conversion-imperial: (1in + 72pt + 6pc);
+ custom-unit: (42octocats * 10);
+ custom-unit-cancelling: (8cats * 9dogs / 4cats);
+ mix-units: (1px + 1em);
+ invalid-units: (1px * 1px);
+}
--- /dev/null
+
+
+.whitespace
+ { color: white; }
+
+.whitespace
+{
+ color: white;
+}
+ .whitespace
+{ color: white; }
+
+.whitespace{color:white;}
+.whitespace { color : white ; }
+
+.white,
+.space,
+.mania
+{ color: white; }
+
+.no-semi-column { color: white }
+.no-semi-column {
+ color: white;
+ white-space: pre
+}
+.no-semi-column {border: 2px solid white}
+.newlines {
+ background: the,
+ great,
+ wall;
+ border: 2px
+ solid
+ black;
+}
+.empty {
+
+}
+.sel
+.newline_ws .tab_ws {
+color:
+white;
+background-position: 45
+-23;
+}
--- /dev/null
+{"version":3,"file":"sourcemaps/basic.css","sources":["testweb/sourcemaps/imported.css","testweb/sourcemaps/basic.less"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;ACAG;EACD,YAAA;EAJA,UAAA;EAWA,iBAAA;EALA,WAAA;EACA,mBAAA;;AAJC,EASC;AATD,EASM;EACL,gBAAA;;AACE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;EAGH,UAAA;;AALJ;AAAK;AAUA;EATL,iBAAA;;AADA,EAEE;AAFG,EAEH;AAFF,EAEO;AAFF,EAEE;AAQF,OARH;AAQG,OARE;EACL,gBAAA;;AACE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFD;AAQF,OARH,GAQG,UARH;AAQG,OARH,GAEM,KAFN;AAQG,OARH,GAQG,UARE;AAQF,OARH,GAEM,KAFD;AAEH,EAFF,GAQG,UARH;AAEE,EAFF,GAQG,UARE;AAQF,OARE,GAQF,UARH;AAQG,OARE,GAEC,KAFN;AAQG,OARE,GAQF,UARE;AAQF,OARE,GAEC,KAFD;AAEH,EAFG,GAQF,UARH;AAEE,EAFG,GAQF,UARE;EAGH,UAAA;;AAKC;EACL,WAAA"}
\ No newline at end of file
--- /dev/null
+<html>
+ <link type="text/css" rel="stylesheet" media="all" href="import.css">
+ <link type="text/css" rel="stylesheet" media="all" href="basic.css">
+<head>
+</head>
+<body>
+<div id="import-test">id import-test</div>
+<div id="import">id import-test</div>
+<div class="imported inline">class imported inline</div>
+<div id="mixin">class mixin</div>
+<div class="a">class a</div>
+<div class="b">class b</div>
+<div class="b">class b<div class="c">class c</div></div>
+<div class="a">class a<div class="d">class d</div></div>
+<div class="extend">class extend<div class="c">class c</div></div>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+body{color:#fff}
\ No newline at end of file
--- /dev/null
+body{color:#fff}#header{background:#fff}#footer{color:#377;background:#233}
\ No newline at end of file
--- /dev/null
+body{color:#fff}
\ No newline at end of file
--- /dev/null
+body {
+ color: #ffffff;
+}
+
+#header {
+ background: #ffffff;
+}
+
+#footer {
+ color: #377;
+ background: #233;
+}
--- /dev/null
+.myRule {
+ background-color: red;
+ width: 5px;
+ background: "Hello";
+}
--- /dev/null
+body {
+ width: 288px;
+ height: 288px;
+ background: transparent url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAwADAAD/7RtYUGhvdG9zaG9wIDMuMAA4QklNBCUAAAAAABAAAAAAAAAAAAAAAAAAAAAAOEJJTQPtAAAAAAAQAAMAAAABAAEAAwAAAAEAAThCSU0EJgAAAAAADgAAAAAAAAAAAAA/gAAAOEJJTQQNAAAAAAAEAAAAHjhCSU0EGQAAAAAABAAAAB44QklNA/MAAAAAAAkAAAAAAAAAAAEAOEJJTQQKAAAAAAABAAA4QklNJxAAAAAAAAoAAQAAAAAAAAACOEJJTQP1AAAAAABIAC9mZgABAGxmZgAGAAAAAAABAC9mZgABAKGZmgAGAAAAAAABADIAAAABAFoAAAAGAAAAAAABADUAAAABAC0AAAAGAAAAAAABOEJJTQP4AAAAAABwAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAADhCSU0ECAAAAAAAEAAAAAEAAAJAAAACQAAAAAA4QklNBB4AAAAAAAQAAAAAOEJJTQQaAAAAAANlAAAABgAAAAAAAAAAAAACKAAABAAAAAAYAEgAYQBwAHAAeQAtAEcAaQBsAG0AbwByAGUALQBCAG8AYgAtAEIAYQByAGsAZQByAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAQAAAACKAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAABAAAAABAAAAAAAAbnVsbAAAAAIAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAAAABUb3AgbG9uZwAAAAAAAAAATGVmdGxvbmcAAAAAAAAAAEJ0b21sb25nAAACKAAAAABSZ2h0bG9uZwAABAAAAAAGc2xpY2VzVmxMcwAAAAFPYmpjAAAAAQAAAAAABXNsaWNlAAAAEgAAAAdzbGljZUlEbG9uZwAAAAAAAAAHZ3JvdXBJRGxvbmcAAAAAAAAABm9yaWdpbmVudW0AAAAMRVNsaWNlT3JpZ2luAAAADWF1dG9HZW5lcmF0ZWQAAAAAVHlwZWVudW0AAAAKRVNsaWNlVHlwZQAAAABJbWcgAAAA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MjoxMi0wNDowMDwveGFwOk1vZGlmeURhdGU+CiAgICAgICAgIDx4YXA6TWV0YWRhdGFEYXRlPjIwMTAtMDYtMTVUMDg6MzI6MTItMDQ6MDA8L3hhcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4YXA6Q3JlYXRvclRvb2w+QWRvYmUgUGhvdG9zaG9wIENTMiBXaW5kb3dzPC94YXA6Q3JlYXRvclRvb2w+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iPgogICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL2pwZWc8L2RjOmZvcm1hdD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyI+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SGlzdG9yeS8+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjMwMDAwLzEwMDAwPC90aWZmOlhSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj4zMDAwMC8xMDAwMDwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICAgICAgICAgPHRpZmY6TmF0aXZlRGlnZXN0PjI1NiwyNTcsMjU4LDI1OSwyNjIsMjc0LDI3NywyODQsNTMwLDUzMSwyODIsMjgzLDI5NiwzMDEsMzE4LDMxOSw1MjksNTMyLDMwNiwyNzAsMjcxLDI3MiwzMDUsMzE1LDMzNDMyOzE1QjRFQUJBOEQ5RTYyMDlCRDJDMjU5REVDODdDOTdDPC90aWZmOk5hdGl2ZURpZ2VzdD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjEwMjQ8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+NTUyPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6Q29sb3JTcGFjZT4tMTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpOYXRpdmVEaWdlc3Q+MzY4NjQsNDA5NjAsNDA5NjEsMzcxMjEsMzcxMjIsNDA5NjIsNDA5NjMsMzc1MTAsNDA5NjQsMzY4NjcsMzY4NjgsMzM0MzQsMzM0MzcsMzQ4NTAsMzQ4NTIsMzQ4NTUsMzQ4NTYsMzczNzcsMzczNzgsMzczNzksMzczODAsMzczODEsMzczODIsMzczODMsMzczODQsMzczODUsMzczODYsMzczOTYsNDE0ODMsNDE0ODQsNDE0ODYsNDE0ODcsNDE0ODgsNDE0OTIsNDE0OTMsNDE0OTUsNDE3MjgsNDE3MjksNDE3MzAsNDE5ODUsNDE5ODYsNDE5ODcsNDE5ODgsNDE5ODksNDE5OTAsNDE5OTEsNDE5OTIsNDE5OTMsNDE5OTQsNDE5OTUsNDE5OTYsNDIwMTYsMCwyLDQsNSw2LDcsOCw5LDEwLDExLDEyLDEzLDE0LDE1LDE2LDE3LDE4LDIwLDIyLDIzLDI0LDI1LDI2LDI3LDI4LDMwOzgzQkQxRDlGMkU2OURFM0E4OTMwNUQ2QkM4Q0I1RkI5PC9leGlmOk5hdGl2ZURpZ2VzdD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz7/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAEgASADASIAAhEBAxEB/8QAHQAAAgMBAQEBAQAAAAAAAAAABQYDBAcIAgEACf/EAEQQAAIBAwMCBAUCBAMHAgQHAAECAwQFEQASIQYxEyJBUQcUMmFxI4EVQlKRCKGxFiQzYnLB0UPhCWOS8BclNESCsvH/xAAbAQACAwEBAQAAAAAAAAAAAAADBAIFBgEHAP/EADMRAAIBAwMCAwYGAwEBAQAAAAECAwAEEQUSITFBEyJRBjJhcZHwFIGhscHRFeHxI0Ki/9oADAMBAAIRAxEAPwDg+HpmsttZC1LJNUxywCc1m53jVCcHbxndnjbqxVGop6iCopoKWoqbbGEnjDFWi3knxAB3U5we2D316la/UlJT76uSveFg/MhWNSVyMDsOexxydCZquoWorrpSK1PVU9OMTRnDlmYLtyPqzk5Ht31kUPjPu61n0Ylv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') 0 0 no-repeat;
+}
--- /dev/null
+body {
+ width: 288px;
+ height: 288px;
+ background: transparent url('include/bob.jpg') 0 0 no-repeat;
+}
--- /dev/null
+body {
+ color: #ffffff;
+}
--- /dev/null
+body {
+ color: #ffffff;
+}
--- /dev/null
+#header {
+ background: #ffffff;
+}
--- /dev/null
+body {
+ color: #ffffff;
+}
--- /dev/null
+#header {
+ background: #ffffff;
+}
--- /dev/null
+body {
+ color: #ffffff;
+}
--- /dev/null
+body {
+ color: #ffffff;
+}
--- /dev/null
+body {
+ color: #ffffff;
+}
--- /dev/null
+#header {
+ background: #ffffff;
+}
--- /dev/null
+.myRule {
+ background-color: get-color(red);
+ width: multiple-args(1px, 4px);
+ background: string-result(3);
+}
--- /dev/null
+body {
+ width: 288px;
+ height: 288px;
+ background: transparent data-uri('include/bob.jpg') 0 0 no-repeat;
+}
--- /dev/null
+@color: #ffffff;
--- /dev/null
+@color: #ffffff;
--- /dev/null
+@import "variables.less";
+body {
+ color: @color;
+}
--- /dev/null
+@import "include/variables.less";
+body {
+ color: @color;
+}
--- /dev/null
+@import "variables.less";
+body {
+ color: @color;
+}
\ No newline at end of file
--- /dev/null
+@import "variables.less";
+#header {
+ background: @color;
+}
--- /dev/null
+#footer {
+ color: #377;
+ background: #233;
+}
--- /dev/null
+@import (less) "include/variablesAsLess.css";
+#header {
+ background: @color;
+}
--- /dev/null
+'use strict';
+
+var grunt = require('grunt');
+var fs = require('fs');
+
+exports.less = {
+ compile: function(test) {
+ test.expect(2);
+
+ var actual = grunt.file.read('tmp/less.css');
+ var expected = grunt.file.read('test/expected/less.css');
+ test.equal(expected, actual, 'should compile less, with the ability to handle imported files from alternate include paths');
+
+ actual = grunt.file.read('tmp/concat.css');
+ expected = grunt.file.read('test/expected/concat.css');
+ test.equal(expected, actual, 'should concat output when passed an array');
+
+ test.done();
+ },
+ compress: function(test) {
+ test.expect(1);
+
+ var actual = grunt.file.read('tmp/compress.css');
+ var expected = grunt.file.read('test/expected/compress.css');
+ test.equal(expected, actual, 'should compress output when compress option is true');
+
+ test.done();
+ },
+ nopaths: function(test) {
+ test.expect(1);
+
+ var actual = grunt.file.read('tmp/nopaths.css');
+ var expected = grunt.file.read('test/expected/nopaths.css');
+ test.equal(expected, actual, 'should default paths to the dirname of the less file');
+
+ test.done();
+ },
+ cleancss: function(test) {
+ test.expect(2);
+
+ var actual = grunt.file.read('tmp/cleancss.css');
+ var expected = grunt.file.read('test/expected/cleancss.css');
+ test.equal(expected, actual, 'should cleancss output when cleancss option is true');
+
+ actual = grunt.file.read('tmp/cleancssReport.css');
+ expected = grunt.file.read('test/expected/cleancssReport.css');
+ test.equal(expected, actual, 'should cleancss output when cleancss option is true and concating is enable');
+
+ test.done();
+ },
+ ieCompat: function(test) {
+ test.expect(2);
+
+ var actual = grunt.file.read('tmp/ieCompatFalse.css');
+ var expected = grunt.file.read('test/expected/ieCompatFalse.css');
+ test.equal(expected, actual, 'should generate data-uris no matter the size when ieCompat option is true');
+
+ actual = grunt.file.read('tmp/ieCompatTrue.css');
+ expected = grunt.file.read('test/expected/ieCompatTrue.css');
+ test.equal(expected, actual, 'should generate data-uris only when under the 32KB mark for Internet Explorer 8');
+
+ test.done();
+ },
+ variablesAsLess: function(test) {
+ test.expect(1);
+
+ var actual = grunt.file.read('tmp/variablesAsLess.css');
+ var expected = grunt.file.read('test/expected/variablesAsLess.css');
+ test.equal(expected, actual, 'should process css files imported less files');
+
+ test.done();
+ },
+ sourceMap: function(test) {
+ test.expect(1);
+
+ var actual = grunt.file.read('tmp/sourceMap.css');
+ test.ok(actual.indexOf('/*# sourceMappingURL=') !== -1, 'compiled file should include a source map.');
+
+ test.done();
+ },
+ sourceMapFilename: function(test) {
+ test.expect(1);
+
+ var sourceMap = grunt.file.readJSON('tmp/sourceMapFilename.css.map');
+ test.equal(sourceMap.sources[0], 'test/fixtures/style3.less', 'should generate a sourceMap with the less file reference.');
+
+ test.done();
+ },
+ sourceMapURL: function(test) {
+ test.expect(1);
+
+ var actual = grunt.file.read('tmp/sourceMapWithCustomURL.css');
+ test.ok(actual.indexOf('/*# sourceMappingURL=custom/url/for/sourceMap.css.map') !== -1, 'compiled file should have a custom source map URL.');
+ test.done();
+ },
+ sourceMapBasepath: function(test) {
+ test.expect(1);
+
+ var sourceMap = grunt.file.readJSON('tmp/sourceMapBasepath.css.map');
+ test.equal(sourceMap.sources[0], 'style3.less', 'should use the basepath for the less file references in the generated sourceMap.');
+
+ test.done();
+ },
+ sourceMapRootpath: function(test) {
+ test.expect(1);
+
+ var sourceMap = grunt.file.readJSON('tmp/sourceMapRootpath.css.map');
+ test.equal(sourceMap.sources[0], 'http://example.org/test/fixtures/style3.less', 'should use the rootpath for the less file references in the generated sourceMap.');
+
+ test.done();
+ },
+ sourceMapLessInline: function(test) {
+ test.expect(1);
+
+ var expected = grunt.file.read('test/fixtures/style3.less');
+ var sourceMap = grunt.file.readJSON('tmp/sourceMapLessInline.css.map');
+ test.equal(sourceMap.sourcesContent[0], expected, 'should put the less file into the generated sourceMap instead of referencing them.');
+
+ test.done();
+ },
+ customFunctions: function(test) {
+ test.expect(1);
+
+ var actual = grunt.file.read('tmp/customFunctions.css');
+ var expected = grunt.file.read('test/expected/customFunctions.css');
+ test.equal(expected, actual, 'should execute custom functions');
+
+ test.done();
+ }
+};
-Subproject commit 8411d228c0928c0a2d6c280a2f39adacc972ee13
+Subproject commit 7b7f19b8680ed7653359bede5833d2cffc11ef8c
templates: {{serialized_templates|safe}},
metadata: [
{key: 'audience', values: [
- 'gimnazjum',
- 'liceum',
- 'master',
- 'minimum',
- 'optimum',
- 'przedszkole',
- 'sp1-3',
- 'sp4-6',
- 'studia'
+ '3-6', '6-9', '9-12', '12-18', '18+', 'Adults'
]},
- {key: 'creator.scenario'},
- {key: 'creator.methodologist'},
- {key: 'description', values: ['Publikacja zrealizowana w ramach projektu Cyfrowa Przyszłość, dofinansowanego ze środków Ministerstwa Kultury i Dziedzictwa Narodowego']},
- {key: 'type', values: ['course', 'synthetic', 'project']},
- {key: 'subject.curriculum', values: [
- '2012/0/wychowanie-przedszkolne/c1',
- '2012/0/wychowanie-przedszkolne/c2',
- '2012/0/wychowanie-przedszkolne/c3',
- '2012/0/wychowanie-przedszkolne/c4',
- '2012/0/wychowanie-przedszkolne/c5',
- '2012/0/wychowanie-przedszkolne/t1',
- '2012/0/wychowanie-przedszkolne/t10',
- '2012/0/wychowanie-przedszkolne/t11',
- '2012/0/wychowanie-przedszkolne/t12',
- '2012/0/wychowanie-przedszkolne/t13',
- '2012/0/wychowanie-przedszkolne/t14',
- '2012/0/wychowanie-przedszkolne/t15',
- '2012/0/wychowanie-przedszkolne/t17',
- '2012/0/wychowanie-przedszkolne/t2',
- '2012/0/wychowanie-przedszkolne/t3',
- '2012/0/wychowanie-przedszkolne/t4',
- '2012/0/wychowanie-przedszkolne/t5',
- '2012/0/wychowanie-przedszkolne/t6',
- '2012/0/wychowanie-przedszkolne/t7',
- '2012/0/wychowanie-przedszkolne/t8',
- '2012/0/wychowanie-przedszkolne/t9',
- '2012/I/edukacja-matematyczna/t1',
- '2012/I/edukacja-plastyczna/t1',
- '2012/I/edukacja-plastyczna/t2',
- '2012/I/edukacja-plastyczna/t3',
- '2012/I/edukacja-plastyczna/t4',
- '2012/I/edukacja-plastyczna/t5',
- '2012/I/edukacja-plastyczna/t6',
- '2012/I/edukacja-polonistyczna/t1',
- '2012/I/edukacja-polonistyczna/t10',
- '2012/I/edukacja-polonistyczna/t11',
- '2012/I/edukacja-polonistyczna/t2',
- '2012/I/edukacja-polonistyczna/t3',
- '2012/I/edukacja-polonistyczna/t4',
- '2012/I/edukacja-polonistyczna/t5',
- '2012/I/edukacja-polonistyczna/t6',
- '2012/I/edukacja-polonistyczna/t7',
- '2012/I/edukacja-polonistyczna/t8',
- '2012/I/edukacja-polonistyczna/t9',
- '2012/I/edukacja-przyrodnicza/t1',
- '2012/I/edukacja-społeczna/t1',
- '2012/I/edukacja-społeczna/t2',
- '2012/I/edukacja-społeczna/t3',
- '2012/I/edukacja-społeczna/t4',
- '2012/I/edukacja-społeczna/t5',
- '2012/I/edukacja-społeczna/t6',
- '2012/I/ETYKA/t1',
- '2012/I/ETYKA/t2',
- '2012/I/ETYKA/t3',
- '2012/II/ETYKA/c1',
- '2012/II/ETYKA/c3',
- '2012/II/ETYKA/C4',
- '2012/II/ETYKA/c5',
- '2012/II/ETYKA/t2',
- '2012/II/ETYKA/t3',
- '2012/II/ETYKA/t4',
- '2012/II/ETYKA/t5',
- '2012/II/ETYKA/t6',
- '2012/II/ETYKA/t7',
- '2012/II/HISTORIA_I_SPOLECZENSTWO/c4',
- '2012/II/HISTORIA_I_SPOLECZENSTWO/t1',
- '2012/II/HISTORIA_I_SPOLECZENSTWO/t4',
- '2012/II/HISTORIA_I_SPOLECZENSTWO/t5',
- '2012/II/HISTORIA_I_SPOLECZENSTWO/t7',
- '2012/III/ETYKA/c1',
- '2012/III/ETYKA/c4',
- '2012/III/ETYKA/t1',
- '2012/III/ETYKA/t11',
- '2012/III/ETYKA/t5',
- '2012/III/ETYKA/t9',
- '2012/III/INFORMATYKA/c1',
- '2012/III/INFORMATYKA/c2',
- '2012/III/INFORMATYKA/c5',
- '2012/III/PLASTYKA/c1',
- '2012/III/PLASTYKA/c2',
- '2012/III/PLASTYKA/c3',
- '2012/III/POLSKI/c1',
- '2012/III/POLSKI/c2',
- '2012/III/POLSKI/c3',
- '2012/III/WOS/c1',
- '2012/III/WOS/c2',
- '2012/III/WOS/c3',
- '2012/III/WOS/c4',
- '2012/III/WOS/c5',
- '2012/III/WOS/t1',
- '2012/III/WOS/t10',
- '2012/III/WOS/t11',
- '2012/III/WOS/t2',
- '2012/III/WOS/t24',
- '2012/III/WOS/t25',
- '2012/III/WOS/t26',
- '2012/III/WOS/t3',
- '2012/III/WOS/t4',
- '2012/III/WOS/t5',
- '2012/III/WOS/t6',
- '2012/III/WOS/t9',
- '2012/II/PLASTYKA/C1',
- '2012/II/PLASTYKA/C2',
- '2012/II/PLASTYKA/C3',
- '2012/II/PLASTYKA/T1',
- '2012/II/PLASTYKA/T2',
- '2012/II/PLASTYKA/T3',
- '2012/II/POLSKI/C1',
- '2012/II/POLSKI/C2',
- '2012/II/POLSKI/C3',
- '2012/II/POLSKI/t1',
- '2012/II/POLSKI/T10',
- '2012/II/POLSKI/T11',
- '2012/II/POLSKI/T12',
- '2012/II/POLSKI/T13',
- '2012/II/POLSKI/T14',
- '2012/II/POLSKI/T15',
- '2012/II/POLSKI/T16',
- '2012/II/POLSKI/T17',
- '2012/II/POLSKI/T18',
- '2012/II/POLSKI/T3',
- '2012/II/POLSKI/T4',
- '2012/II/POLSKI/T5',
- '2012/II/POLSKI/T6',
- '2012/II/POLSKI/T7',
- '2012/II/POLSKI/T9',
- '2012/II/WYCHOWANIE_DO_ZYCIA_W_RODZINIE/T10',
- '2012/II/WYCHOWANIE_DO_ZYCIA_W_RODZINIE/T11',
- '2012/II/WYCHOWANIE_DO_ZYCIA_W_RODZINIE/T13',
- '2012/II/ZAJECIA_KOMPUTEROWE/c1',
- '2012/II/ZAJECIA_KOMPUTEROWE/c2',
- '2012/II/ZAJECIA_KOMPUTEROWE/c3',
- '2012/II/ZAJECIA_KOMPUTEROWE/c5',
- '2012/II/ZAJECIA_KOMPUTEROWE/T4',
- '2012/II/ZAJECIA_KOMPUTEROWE/T6',
- '2012/II/ZAJECIA_KOMPUTEROWE/T7',
- '2012/IV/ETYKA/c1',
- '2012/IV/ETYKA/c2',
- '2012/IV/ETYKA/t11',
- '2012/IV/ETYKA/t4',
- '2012/IV/ETYKA/t6',
- '2012/IV/ETYKA/t8',
- '2012/IV/INFORMATYKA/c1',
- '2012/IV/INFORMATYKA/c2',
- '2012/IV/INFORMATYKA/c4',
- '2012/IV/INFORMATYKA/c5',
- '2012/IV/INFORMATYKA/t1',
- '2012/IV/INFORMATYKA/t2',
- '2012/IV/INFORMATYKA/t3',
- '2012/IV/INFORMATYKA/t4',
- '2012/IV/INFORMATYKA/t7',
- '2012/IV/POLSKI/c1',
- '2012/IV/POLSKI/c2',
- '2012/IV/POLSKI/c3',
- '2012/IV/WOK/c1',
- '2012/IV/WOK/c2',
- '2012/IV/WOK/c3',
- '2012/IV/WOS/c1',
- '2012/IV/WOS/c2',
- '2012/IV/WOS/c3',
- '2012/IV/WOS/c4',
- '2012/IV/WOS/c6',
- '2012/IV/WOS/c6/roz',
- '2012/IV/WOS/t1',
- '2012/IV/WOS/t10/roz',
- '2012/IV/WOS/t13/roz',
- '2012/IV/WOS/t14/roz',
- '2012/IV/WOS/t15/roz',
- '2012/IV/WOS/t2',
- '2012/IV/WOS/t27/roz',
- '2012/IV/WOS/t2/roz',
- '2012/IV/WOS/t3',
- '2012/IV/WOS/t32/roz',
- '2012/IV/WOS/t36/roz',
- '2012/IV/WOS/t5',
- '2012/IV/WOS/t6',
- '2012/IV/WOS/t8/roz',
- '2012/I/wychowanie-przedszkolne/t16',
- '2012/I/ZAJECIA_KOMPUTEROWE/t1',
- '2012/I/ZAJECIA_KOMPUTEROWE/t2',
- '2012/I/ZAJECIA_KOMPUTEROWE/t3',
- '2012/I/ZAJECIA_KOMPUTEROWE/t4',
- '2012/I/ZAJECIA_KOMPUTEROWE/t5'
- ]},
- {key: 'subject.competence', values: [
- 'Anonimowość - Bezpieczeństwo w komunikacji i mediach',
- 'Bezpieczeństwo komunikacji, pracy i transakcji - Bezpieczeństwo w komunikacji i mediach',
- 'Finansowanie mediów i wybrane sposoby zarabiania w nowych mediach - Ekonomiczne aspekty działania mediów',
- 'Funkcje komunikatów medialnych - Język mediów',
- 'Informacja jako dobro ekonomiczne - Ekonomiczne aspekty działania mediów',
- 'Językowa natura mediów - Język mediów',
- 'Komunikacja i media jako przedmiot refleksji etycznej - Etyka i wartości w komunikacji i mediach',
- 'Komunikacja - Relacje w środowisku medialnym',
- 'Kultura komunikacji medialnej - Język mediów',
- 'Media a prawa człowieka, obywatela i dziecka - Prawo w komunikacji i mediach',
- 'Nadzór nad siecią - Bezpieczeństwo w komunikacji i mediach',
- 'Ochrona danych osobowych - Prawo w komunikacji i mediach',
- 'Ochrona prywatności i wizerunku - Bezpieczeństwo w komunikacji i mediach',
- 'Otoczenie - Relacje w środowisku medialnym',
- 'Podejście krytyczne do informacji - Korzystanie z informacji',
- 'Polityka medialna - Ekonomiczne aspekty działania mediów',
- 'Prawa osób niepełnosprawnych - Prawo w komunikacji i mediach',
- 'Prawa wyłączne i monopole intelektualne - Prawo w komunikacji i mediach',
- 'Prawo mediów i media publiczne - Prawo w komunikacji i mediach',
- 'Prawo telekomunikacyjne - Prawo w komunikacji i mediach',
- 'Prezentowanie - Kreatywne korzystanie z mediów',
- 'Przetwarzanie - Kreatywne korzystanie z mediów',
- 'Rodzaje, źródła i praktyka stosowania prawa w kontekście mediów - Prawo w komunikacji i mediach',
- 'Rynek mediów - Ekonomiczne aspekty działania mediów',
- 'Tworzenie - Kreatywne korzystanie z mediów',
- 'Uzależnienia i higiena korzystania z mediów - Bezpieczeństwo w komunikacji i mediach',
- 'Wizerunek - Relacje w środowisku medialnym',
- 'Wykorzystanie informacji - Korzystanie z informacji',
- 'Wyszukiwanie informacji - Korzystanie z informacji',
- 'Wyzwania etyczne a normy prawa w mediach i komunikacji - Etyka i wartości w komunikacji i mediach',
- 'Wyzwania etyczne a treści mediów i komunikacji - Etyka i wartości w komunikacji i mediach',
- 'Wyzwania etyczne w relacjach przez media - Etyka i wartości w komunikacji i mediach',
- 'Źródła informacji - Korzystanie z informacji'
- ]}
+ {key: 'creator'},
+ {key: 'description'},
+ {key: 'publisher'},
+ {key: 'language'},
+ {key: 'rights'},
+ {key: 'relation.coverimage.url', isFile: true}
]
}
},
[
{actionName: 'core.switchToHeader'},
{actionName: 'core.switchToParagraph'},
+ {actionName: 'core.switchToImage'},
'core.toggleBulletList', 'core.toggleEnumList'
],
[
- {actionName: 'core.emphasis', actionConfig: {label: 'wyróżnienie'}},
- {actionName: 'core.cite', actionConfig: {label: 'cytat'}},
+ {actionName: 'core.emphasis', actionConfig: {label: 'emphasis'}},
+ {actionName: 'core.cite', actionConfig: {label: 'cite'}},
{actionName: 'core.link'}
],
- ['core.template'],
+ //['core.template'],
+
['core.showMetadataEditor'],
- [
+ /*[
'core.insertOrderExercise',
'core.insertChoiceSingleExercise',
'core.insertChoiceMultiExercise',
'core.insertChoiceTrueOrFalseExercise',
'core.insertGapsExercise',
'core.insertReplaceExercise'
- ]
+ ]*/
],
documentSaveUrl: function(id) { return '/editor/text/' + id + '/'; },
documentHistoryUrl: function(id) { return '/editor/history/' + id + '/'},
documentDiffUrl: function(id) { return '/editor/diff/' + id + '/'; },
documentRestoreUrl: function(id) { return '/editor/revert/' + id + '/'},
+ documentPublishUrl: '{% url "catalogue_publish" pk %}',
+
+ documentScheduleUrl: '{% url "catalogue_book_schedule" pk %}',
+ documentForkUrl: '{% url "catalogue_fork" pk %}',
+ documentGalleryUrl: '{% url "catalogue_book_gallery" pk %}',
+
documentAttachmentUrl: function(attachmentName) {
- return '{{MEDIA_URL}}{{IMAGE_DIR}}{{slug}}/' + attachmentName;
+ return '{{MEDIA_URL}}uploads/{{ pk }}/' + attachmentName;
},
documentUrl: function(id, version) {
- var url = '{% url 'wiki_editor' slug %}'
+ var url = '{% url 'wiki_editor' pk %}'
if(version) {
url = url + '?version=' + version;
}
return url;
},
+ documentPreviewUrl: function(id) { return '/documents/{{ pk }}/rev' + id + '/preview/' },
+ documentPreviewMainUrl: function(id) { return '/documents/{{ pk }}/preview/' },
documentSaveForm: {
fields: [
{label: '{{forms.text_save.comment.label}}', name: '{{forms.text_save.comment.html_name}}', type: 'textarea'},
- {label: '{{forms.text_save.stage_completed.label}}', name: '{{forms.text_save.stage_completed.html_name}}', type: 'select', options: [
- {% for value,text in tags %}
+ {label: '{{forms.text_save.stage.label}}', name: '{{forms.text_save.stage.html_name}}', type: 'select', options: [
+ {% for value, text in forms.text_save.stage.field.choices %}
{value:'{{value|default:''}}', text:'{{text}}'} {% if not forloop.last %}, {% endif %}
{% endfor %}
- ], description: '{{forms.text_save.stage_completed.help_text}}'},
+ ], description: '{{forms.text_save.stage.help_text}}'},
],
content_field_name: '{{forms.text_save.text.html_name}}',
version_field_name: '{{forms.text_save.parent_revision.html_name}}'
{label: '{{forms.text_revert.comment.label}}', name: '{{forms.text_revert.comment.html_name}}', type: 'textarea'}
],
version_field_name: '{{forms.text_revert.revision.html_name}}'
+ },
+
+ documentPublishForm: {
+ fields: [
+ ],
+ revision_field_name: '{{forms.text_publish.revision.html_name}}'
}
+
};
{% if can_pubmark %}
Editor.setBootstrappedData('data', data);
Editor.start(config);
};
+
+
+
+
</script>
<div id="editor_root"></div>
</body>
{% load i18n %}
-<div id="save_dialog" class="dialog" data-ui-jsclass="SaveDialog">
+<div id="DATOLDsave_dialog" class="dialog" data-ui-jsclass="SaveDialog">
<form method="POST" action="">
{% csrf_token %}
<p>{{ forms.text_save.comment.label }}</p>
</table>
{% else %}
<p>
- {{ forms.text_save.stage_completed.label }}:
- {{ forms.text_save.stage_completed }}
- <span class="help_text">{{ forms.text_save.stage_completed.help_text }}</span>
- <span data-ui-error-for="{{ forms.text_save.stage_completed.name }}"> </span>
+ {{ forms.text_save.stage.label }}:
+ {{ forms.text_save.stage }}
+ <br><span class="help_text">{{ forms.text_save.stage.help_text }}</span>
+ <span data-ui-error-for="{{ forms.text_save.stage.name }}"> </span>
</p>
{% if can_pubmark %}
<p>
urlpatterns = patterns('wiki.views',
- url(r'^edit/(?P<slug>[^/]+)/(?:(?P<chunk>[^/]+)/)?$',
+ url(r'^edit/(?P<pk>[^/]+)/$',
'editor', name="wiki_editor"),
- url(r'^readonly/(?P<slug>[^/]+)/(?:(?P<chunk>[^/]+)/)?$',
+ url(r'^readonly/(?P<pk>[^/]+)/$',
'editor_readonly', name="wiki_editor_readonly"),
url(r'^gallery/(?P<directory>[^/]+)/$',
'gallery', name="wiki_gallery"),
- url(r'^history/(?P<chunk_id>\d+)/$',
+ url(r'^history/(?P<doc_id>\d+)/$',
'history', name="wiki_history"),
url(r'^rev/(?P<chunk_id>\d+)/$',
'revision', name="wiki_revision"),
- url(r'^text/(?P<chunk_id>\d+)/$',
+ url(r'^text/(?P<doc_id>\d+)/$',
'text', name="wiki_text"),
- url(r'^revert/(?P<chunk_id>\d+)/$',
+ url(r'^revert/(?P<doc_id>\d+)/$',
'revert', name='wiki_revert'),
- url(r'^diff/(?P<chunk_id>\d+)/$', 'diff', name="wiki_diff"),
- url(r'^pubmark/(?P<chunk_id>\d+)/$', 'pubmark', name="wiki_pubmark"),
-
- url(r'^themes$', 'themes', name="themes"),
+ url(r'^diff/(?P<doc_id>\d+)/$', 'diff', name="wiki_diff"),
)
from datetime import datetime
+import json
import os
import logging
import urllib
from django.utils.translation import ugettext as _
from django.views.decorators.http import require_POST, require_GET
from django.shortcuts import get_object_or_404, render
-from django.utils import simplejson
from django.contrib.auth.decorators import login_required
-from catalogue.models import Book, Chunk, Template
+from catalogue.models import Document, Template
+from dvcs.models import Revision
import nice_diff
from wiki import forms
from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError,
ajax_require_permission)
-from wiki.models import Theme
#
# Quick hack around caching problems, TODO: use ETags
MAX_LAST_DOCS = 10
-def get_history(chunk):
- changes = []
- for change in chunk.history():
- changes.append({
- "version": change.revision,
- "description": change.description,
- "author": change.author_str(),
- "date": localize(change.created_at),
- "publishable": _("Publishable") + "\n" if change.publishable else "",
- "tag": ',\n'.join(unicode(tag) for tag in change.tags.all()),
+def get_history(document):
+ revisions = []
+ for i, revision in enumerate(document.history()):
+ revisions.append({
+ "version": i + 1,
+ "description": revision.description,
+ "author": revision.author_str(),
+ "date": localize(revision.created_at),
+ "published": "",
+ "revision": revision.pk,
"published": _("Published") + ": " + \
- localize(change.publish_log.order_by('-book_record__timestamp')[0].book_record.timestamp) \
- if change.publish_log.exists() else "",
+ localize(revision.publish_log.order_by('-timestamp')[0].timestamp) \
+ if revision.publish_log.exists() else "",
})
- return changes
+ return revisions
@never_cache
-@login_required
-def editor(request, slug, chunk=None, template_name='wiki/bootstrap.html'):
- try:
- chunk = Chunk.get(slug, chunk)
- except Chunk.MultipleObjectsReturned:
- # TODO: choice page
- raise Http404
- except Chunk.DoesNotExist:
- if chunk is None:
- try:
- book = Book.objects.get(slug=slug)
- except Book.DoesNotExist:
- return http.HttpResponseRedirect(reverse("catalogue_create_missing", args=[slug]))
- else:
- raise Http404
- if not chunk.book.accessible(request):
- return HttpResponseForbidden("Not authorized.")
+#@login_required
+def editor(request, pk, chunk=None, template_name='wiki/bootstrap.html'):
+ doc = get_object_or_404(Document, pk=pk, deleted=False)
+ #~ if not doc.accessible(request):
+ #~ return HttpResponseForbidden("Not authorized.")
access_time = datetime.now()
- last_books = request.session.get("wiki_last_books", {})
- last_books[slug, chunk.slug] = {
- 'time': access_time,
- 'title': chunk.pretty_name(),
- }
-
- if len(last_books) > MAX_LAST_DOCS:
- oldest_key = min(last_books, key=lambda x: last_books[x]['time'])
- del last_books[oldest_key]
- request.session['wiki_last_books'] = last_books
save_form = forms.DocumentTextSaveForm(user=request.user, prefix="textsave")
try:
except:
version = None
if version:
- text = chunk.at_revision(version).materialize()
+ text = doc.at_revision(version).materialize()
else:
- text = chunk.materialize()
+ text = doc.materialize()
+ revision = doc.revision
+ history = get_history(doc)
return render(request, template_name, {
- 'serialized_document_data': simplejson.dumps({
+ 'serialized_document_data': json.dumps({
'document': text,
- 'document_id': chunk.id,
- 'title': chunk.book.title,
- 'history': get_history(chunk),
- 'version': version or chunk.revision(),
- 'stage': chunk.stage.name if chunk.stage else None,
- 'assignment': chunk.user.username if chunk.user else None
+ 'document_id': doc.pk,
+ 'title': doc.meta().get('title', ''),
+ 'history': history,
+ 'version': len(history), #version or chunk.revision(),
+ 'revision': revision.pk,
+ 'stage': doc.stage,
+ 'assignment': str(doc.assigned_to),
}),
- 'serialized_templates': simplejson.dumps([
+ 'serialized_templates': json.dumps([
{'id': t.id, 'name': t.name, 'content': t.content} for t in Template.objects.filter(is_partial=True)
]),
'forms': {
"text_save": save_form,
- "text_revert": forms.DocumentTextRevertForm(prefix="textrevert")
+ "text_revert": forms.DocumentTextRevertForm(prefix="textrevert"),
+ "text_publish": forms.DocumentTextPublishForm(prefix="textpublish"),
},
- 'tags': list(save_form.fields['stage_completed'].choices),
- 'can_pubmark': request.user.has_perm('catalogue.can_pubmark'),
- 'slug': chunk.book.slug
+ 'pk': doc.pk,
})
@never_cache
@decorator_from_middleware(GZipMiddleware)
-def text(request, chunk_id):
- doc = get_object_or_404(Chunk, pk=chunk_id)
- if not doc.book.accessible(request):
- return HttpResponseForbidden("Not authorized.")
+def text(request, doc_id):
+ doc = get_object_or_404(Document, pk=doc_id, deleted=False)
+ #~ if not doc.book.accessible(request):
+ #~ return HttpResponseForbidden("Not authorized.")
if request.method == 'POST':
form = forms.DocumentTextSaveForm(request.POST, user=request.user, prefix="textsave")
else:
author = None
text = form.cleaned_data['text']
- parent_revision = form.cleaned_data['parent_revision']
- if parent_revision is not None:
- parent = doc.at_revision(parent_revision)
- else:
- parent = None
- stage = form.cleaned_data['stage_completed']
- tags = [stage] if stage else []
- publishable = (form.cleaned_data['publishable'] and
- request.user.has_perm('catalogue.can_pubmark'))
- doc.commit(author=author,
+ #~ parent_revision = form.cleaned_data['parent_revision']
+ #~ if parent_revision is not None:
+ #~ parent = doc.at_revision(parent_revision)
+ #~ else:
+ #~ parent = None
+ stage = form.cleaned_data['stage']
+ #~ tags = [stage] if stage else []
+ #~ publishable = (form.cleaned_data['publishable'] and
+ #~ request.user.has_perm('catalogue.can_pubmark'))
+ try:
+ doc.commit(author=author,
text=text,
- parent=parent,
+ parent=False,
description=form.cleaned_data['comment'],
- tags=tags,
author_name=form.cleaned_data['author_name'],
author_email=form.cleaned_data['author_email'],
- publishable=publishable,
)
- revision = doc.revision()
+ doc.set_stage(stage)
+ except:
+ from traceback import print_exc
+ print_exc()
+ raise
+ #revision = doc.revision()
return JSONResponse({
- 'text': doc.materialize() if parent_revision != revision else None,
- 'version': revision,
- 'stage': doc.stage.name if doc.stage else None,
- 'assignment': doc.user.username if doc.user else None
+ 'text': None, #doc.materialize() if parent_revision != revision else None,
+ #'version': revision,
+ #'stage': doc.stage.name if doc.stage else None,
+ 'assignment': doc.assigned_to.username if doc.assigned_to else None
})
else:
return JSONFormInvalid(form)
@never_cache
@require_POST
-def revert(request, chunk_id):
+def revert(request, doc_id):
form = forms.DocumentTextRevertForm(request.POST, prefix="textrevert")
if form.is_valid():
- doc = get_object_or_404(Chunk, pk=chunk_id)
- if not doc.book.accessible(request):
- return HttpResponseForbidden("Not authorized.")
-
- revision = form.cleaned_data['revision']
+ doc = get_object_or_404(Document, pk=doc_id, deleted=False)
+ rev = get_object_or_404(Revision, pk=form.cleaned_data['revision'])
comment = form.cleaned_data['comment']
- comment += "\n#revert to %s" % revision
+ comment += "\n#revert to %s" % rev.pk
if request.user.is_authenticated():
author = request.user
else:
author = None
- before = doc.revision()
- logger.info("Reverting %s to %s", chunk_id, revision)
- doc.at_revision(revision).revert(author=author, description=comment)
+ #before = doc.revision
+ logger.info("Reverting %s to %s", doc_id, rev.pk)
+
+ doc.commit(author=author,
+ text=rev.materialize(),
+ parent=False, #?
+ description=comment,
+ #author_name=form.cleaned_data['author_name'], #?
+ #author_email=form.cleaned_data['author_email'], #?
+ )
return JSONResponse({
- 'document': doc.materialize() if before != doc.revision() else None,
- 'version': doc.revision(),
+ #'document': None, #doc.materialize() if before != doc.revision else None,
+ #'version': doc.revision(),
})
else:
return JSONFormInvalid(form)
@never_cache
def gallery(request, directory):
+ if not request.user.is_authenticated():
+ return HttpResponseForbidden("Not authorized.")
+
try:
base_url = ''.join((
smart_unicode(settings.MEDIA_URL),
images = [map_to_url(f) for f in map(smart_unicode, os.listdir(base_dir)) if is_image(f)]
images.sort()
- if not request.user.is_authenticated():
- return HttpResponseForbidden("Not authorized.")
-
return JSONResponse(images)
except (IndexError, OSError):
logger.exception("Unable to fetch gallery")
@never_cache
-def diff(request, chunk_id):
+def diff(request, doc_id):
revA = int(request.GET.get('from', 0))
revB = int(request.GET.get('to', 0))
if revB == 0:
revB = None
- doc = get_object_or_404(Chunk, pk=chunk_id)
- if not doc.book.accessible(request):
- return HttpResponseForbidden("Not authorized.")
+ # TODO: check if revisions in line.
+
+ doc = get_object_or_404(Document, pk=doc_id, deleted=False)
# allow diff from the beginning
if revA:
- docA = doc.at_revision(revA).materialize()
+ docA = Revision.objects.get(pk=revA).materialize()
else:
docA = ""
- docB = doc.at_revision(revB).materialize()
+ docB = Revision.objects.get(pk=revB).materialize()
return http.HttpResponse(nice_diff.html_diff_table(docA.splitlines(),
docB.splitlines(), context=3))
@never_cache
-def history(request, chunk_id):
+def history(request, doc_id):
# TODO: pagination
- doc = get_object_or_404(Chunk, pk=chunk_id)
- if not doc.book.accessible(request):
- return HttpResponseForbidden("Not authorized.")
+ doc = get_object_or_404(Document, pk=doc_id, deleted=False)
return JSONResponse(get_history(doc))
-
-
-@require_POST
-@ajax_require_permission('catalogue.can_pubmark')
-def pubmark(request, chunk_id):
- form = forms.DocumentPubmarkForm(request.POST, prefix="pubmark")
- if form.is_valid():
- doc = get_object_or_404(Chunk, pk=chunk_id)
- if not doc.book.accessible(request):
- return HttpResponseForbidden("Not authorized.")
-
- revision = form.cleaned_data['revision']
- publishable = form.cleaned_data['publishable']
- change = doc.at_revision(revision)
- if publishable != change.publishable:
- change.set_publishable(publishable)
- return JSONResponse({"message": _("Revision marked")})
- else:
- return JSONResponse({"message": _("Nothing changed")})
- else:
- return JSONFormInvalid(form)
-
-
-def themes(request):
- prefix = request.GET.get('q', '')
- return http.HttpResponse('\n'.join([str(t) for t in Theme.objects.filter(name__istartswith=prefix)]))
-Subproject commit 85ab1e2d4b363dc747eacda08fa254586905cf25
+Subproject commit 3f64488684dbf14b7e0005209d2dca878e1852d9
'STATIC_URL': settings.STATIC_URL,
'IMAGE_DIR': settings.IMAGE_DIR,
'DEBUG': settings.DEBUG,
- 'RAVEN_CONFIG': getattr(settings, 'RAVEN_CONFIG'),
+ 'RAVEN_CONFIG': getattr(settings, 'RAVEN_CONFIG', None),
'APP_VERSION': VERSION,
}
--- /dev/null
+from django import forms
+
+class RegistrationForm(forms.Form):
+ first_name = forms.CharField()
+ last_name = forms.CharField()
+ email = forms.EmailField()
+ password = forms.CharField(widget=forms.PasswordInput)
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2011-10-18 11:18+0200\n"
+"POT-Creation-Date: 2016-01-26 11:21+0100\n"
"PO-Revision-Date: 2011-10-18 11:19+0100\n"
"Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: templates/404.html:8
+#: templates/404.html:7
msgid "Page not found"
msgstr "Strona nie została znaleziona"
-#: templates/404.html:10
+#: templates/404.html:9
#, python-format
msgid ""
"The page you're trying\n"
"to reach (<tt>%(p)s</tt>) could not be found. If it's a document, try\n"
"looking for it on the <a href=\"/\">document list</a>."
-msgstr "Strona, do której próbujesz dotrzeć (<tt>%(p)s</tt>), nie istnieje. Spróbuj poszukać jej na <a href=\"/\">liście dokumentów</a>."
+msgstr ""
+"Strona, do której próbujesz dotrzeć (<tt>%(p)s</tt>), nie istnieje. Spróbuj "
+"poszukać jej na <a href=\"/\">liście dokumentów</a>."
-#: templates/404.html:15
+#: templates/404.html:14
#, python-format
msgid ""
"If you\n"
"still can't find what you're looking for, please\n"
"<a href=\"%(m)s\">contact the administrator</a>."
-msgstr "Jeśli nadal nie możesz znaleźć tego, czego szukasz, <a href=\"%(m)s\">skontaktuj się z administratorem</a>."
+msgstr ""
+"Jeśli nadal nie możesz znaleźć tego, czego szukasz, <a href=\"%(m)s"
+"\">skontaktuj się z administratorem</a>."
-#: templates/404.html:22
+#: templates/404.html:21
#, python-format
msgid ""
"If you're coming from Redmine, please note that\n"
"Go to the <a href=\"/\">document list</a>\n"
"or to <a href=\"%(m)s\">your page</a> instead."
msgstr ""
-"Jeśli skierował Cię tu Redmine, zwróć uwagę, że nie służy on już do koordynowania prac redakcyjnych. Możesz za to przejść do <a href=\"/\">listy dokumentów</a>\n"
+"Jeśli skierował Cię tu Redmine, zwróć uwagę, że nie służy on już do "
+"koordynowania prac redakcyjnych. Możesz za to przejść do <a href=\"/\">listy "
+"dokumentów</a>\n"
"albo do <a href=\"%(m)s\">swojej strony</a>."
#: templates/base.html:7
msgid "Loading"
msgstr "Ładowanie"
-#: templates/admin/index.html:21
-#, python-format
-msgid "Models available in the %(name)s application."
+#: templates/main.html:20
+msgid ""
+"\n"
+" <p>Media & Information Literacy<br>\n"
+" Platform for Exchanging<br>\n"
+" Educational Resources<br>\n"
+" </p>\n"
msgstr ""
-#: templates/admin/index.html:22
-#, python-format
-msgid "%(name)s"
-msgstr ""
+#: templates/main.html:26
+msgid "Create and share your resources<br>Start tutorial"
+msgstr "Twórz zasoby i dziel się nimi<br>Zobacz przewodnik"
-#: templates/admin/index.html:32
-msgid "Add"
-msgstr ""
+#: templates/main.html:29 templates/registration/head_login.html:17
+msgid "Competition"
+msgstr "Konkurs"
-#: templates/admin/index.html:38
-msgid "Change"
-msgstr ""
+#: templates/main.html:30
+msgid "Joint project by:"
+msgstr "Wspólny projekt:"
-#: templates/admin/index.html:48
-msgid "You don't have permission to edit anything."
-msgstr ""
+#: templates/main.html:47
+msgid "See active organizations and join"
+msgstr "Zobacz aktywne organizacje i dołącz"
-#: templates/admin/index.html:56
-msgid "Recent Actions"
-msgstr ""
+#: templates/main.html:61
+msgid "More organizations"
+msgstr "Więcej organizacji"
-#: templates/admin/index.html:57
-msgid "My Actions"
-msgstr ""
+#: templates/main.html:69
+msgid "Finished resources"
+msgstr "Ukończone zasoby"
-#: templates/admin/index.html:61
-msgid "None available"
-msgstr ""
+#: templates/main.html:85 templates/main.html.py:115
+msgid "Audience"
+msgstr "Przeznaczone dla"
+
+#: templates/main.html:87 templates/main.html.py:117
+msgid "Owner"
+msgstr "Właściciel"
+
+#: templates/main.html:94
+msgid "More finished resources"
+msgstr "Więcej ukończonych zasobów"
-#: templates/admin/index.html:75
-msgid "Unknown content"
+#: templates/main.html:99
+msgid "Upcoming resources"
+msgstr "Nadchodzące zasoby"
+
+#: templates/main.html:124
+msgid "More upcoming resources"
+msgstr "Więcej nadchodzących zasobów"
+
+#: templates/main.html:134
+msgid "Tutorial mode"
+msgstr "Tryb przewodnika"
+
+#: templates/main.html:135
+msgid ""
+"Tutorial will help you navigate through the platform. You can disable it at "
+"any point."
msgstr ""
+"Przewodnik pomoże Ci odnaleźć się na platformie. Możesz go wyłączyć w "
+"dowolnej chwili."
#: templates/pagination/pagination.html:5
#: templates/pagination/pagination.html:7
msgid "next"
msgstr "następne"
-#: templates/registration/head_login.html:5
-msgid "Log Out"
+#: templates/registration.html:8 templates/registration.html.py:27
+#: templates/registration/head_login.html:104
+msgid "Register"
+msgstr "Załóż konto"
+
+#: templates/registration.html:14
+msgid "First name"
+msgstr "Imię"
+
+#: templates/registration.html:17
+msgid "Last name"
+msgstr "Nazwisko"
+
+#: templates/registration.html:20 templates/registration/head_login.html:85
+msgid "E-mail"
+msgstr "E-mail"
+
+#: templates/registration.html:23
+msgid "Your new password"
+msgstr "Nowe hasło"
+
+#: templates/registration/head_login.html:16
+msgid "About"
+msgstr "O platformie"
+
+#: templates/registration/head_login.html:21
+msgid "Create an organization here. Organizations help manage team's work."
+msgstr ""
+"Tu możesz utworzyć organizację. Organizacje pomagają zarządzać pracą zespołu."
+
+#: templates/registration/head_login.html:23
+#: templates/registration/head_login.html:53
+msgid "New organization"
+msgstr "Nowa organizacja"
+
+#: templates/registration/head_login.html:27
+msgid "Start editing a new resource."
+msgstr "Zacznij edytować nowy zasób."
+
+#: templates/registration/head_login.html:29
+msgid "New resource"
+msgstr "Nowy zasób"
+
+#: templates/registration/head_login.html:32
+msgid "My resources"
+msgstr "Moje zasoby"
+
+#: templates/registration/head_login.html:46
+msgid "My organizations"
+msgstr "Moje organizacje"
+
+#: templates/registration/head_login.html:51
+msgid "You are not a member of any organizations"
+msgstr "Nie należysz do żadnej organizacji"
+
+#: templates/registration/head_login.html:55
+msgid "Edit my data"
+msgstr "Moje dane"
+
+#: templates/registration/head_login.html:57
+msgid "Admin"
+msgstr "Admin"
+
+#: templates/registration/head_login.html:59
+msgid "Logout"
msgstr "Wyloguj"
-#: templates/registration/head_login.html:9
-msgid "Log In"
+#: templates/registration/head_login.html:70
+msgid "First, you will need an account."
+msgstr "Najpierw potrzebujesz konta."
+
+#: templates/registration/head_login.html:74
+#: templates/registration/head_login.html:79
+msgid "Log in / Register"
+msgstr "Zaloguj / Załóż konto"
+
+#: templates/registration/head_login.html:89
+msgid "Password"
+msgstr "Hasło"
+
+#: templates/registration/head_login.html:97
+#: templates/registration/password_reset_complete.html:9
+msgid "Log in"
msgstr "Logowanie"
+#: templates/registration/head_login.html:101
+msgid "I forgot my password"
+msgstr "Nie pamiętam hasła"
+
+#: templates/registration/head_login.html:103
+msgid "<strong>Register now</strong> to start editing your own materials."
+msgstr ""
+"<strong>Załóż konto teraz</strong> by zacząć edytować własne materiały."
+
+#: templates/registration/password_reset_complete.html:7
+msgid "Your password has been set. You may go ahead and log in now."
+msgstr "Hasło zostało ustawione. Możesz się teraz zalogować."
+
+#: templates/registration/password_reset_confirm.html:10
+msgid ""
+"Please enter your new password twice so we can verify you typed it in "
+"correctly."
+msgstr ""
+"Wpisz swoje hasło dwukrotnie, by potwierdzić, że zostało podane prawidłowo."
+
+#: templates/registration/password_reset_confirm.html:14
+msgid "Change my password"
+msgstr "Zmień hasło"
+
+#: templates/registration/password_reset_confirm.html:19
+msgid ""
+"The password reset link was invalid, possibly because it has already been "
+"used. Please request a new password reset."
+msgstr ""
+"Link odzyskiwania hasła jest nieprawidłowy, być może został już "
+"wykorzystany. Spróbuj ponownie wybrać opcję odzyskiwania hasła."
+
+#: templates/registration/password_reset_done.html:7
+msgid ""
+"We've emailed you instructions for setting your password, if an account "
+"exists with the email you entered. You should receive them shortly."
+msgstr ""
+"Wysłaliśmy instrukcję odzyskiwania hasła na podany e-mail, o ile istnieje "
+"dla niego konto. E-mail powinien dotrzeć wkrótce."
+
+#: templates/registration/password_reset_done.html:9
+msgid ""
+"If you don't receive an email, please make sure you've entered the address "
+"you registered with, and check your spam folder."
+msgstr ""
+"Jeśli e-mail nie dotrze, prosimy o sprawdzenie, czy jest to adres użyty do "
+"założenia konta, oraz o sprawdzenie folderu spamu w skrzynce pocztowej."
+
+#~ msgid "Log Out"
+#~ msgstr "Wyloguj"
+
#~ msgid "Refresh panel"
#~ msgstr "Odśwież panel"
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
-LANGUAGE_CODE = 'pl'
+LANGUAGE_CODE = 'en'
#import locale
#locale.setlocale(locale.LC_ALL, '')
# to load the internationalization machinery.
USE_I18N = True
USE_L10N = True
-
+LANGUAGES = [
+ ('en', 'English'),
+ ('pl', 'polski'),
+]
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
PROJECT_ROOT + '/static/'
]
+LOCALE_PATHS = [
+ PROJECT_ROOT + '/locale/',
+]
+
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django_cas.middleware.CASMiddleware',
+ #'django_cas.middleware.CASMiddleware',
- 'django.middleware.doc.XViewMiddleware',
+ 'django.contrib.admindocs.middleware.XViewMiddleware',
'pagination.middleware.PaginationMiddleware',
- 'maintenancemode.middleware.MaintenanceModeMiddleware',
+ #'maintenancemode.middleware.MaintenanceModeMiddleware',
+ 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
)
-AUTHENTICATION_BACKENDS = (
- 'django.contrib.auth.backends.ModelBackend',
- 'fnpdjango.auth_backends.AttrCASBackend',
-)
+#AUTHENTICATION_BACKENDS = (
+# 'django.contrib.auth.backends.ModelBackend',
+# 'fnpdjango.auth_backends.AttrCASBackend',
+#)
ROOT_URLCONF = 'redakcja.urls'
'django.contrib.sites',
'django.contrib.admin',
'django.contrib.admindocs',
- 'django.contrib.comments',
+ 'django.contrib.flatpages',
+ #'django.contrib.comments',
- 'south',
+ #'south',
'sorl.thumbnail',
'pagination',
- 'gravatar',
- 'djcelery',
- 'djkombu',
+ #'gravatar',
+ #'kombu.transport.django',
'fileupload',
'pipeline',
'catalogue',
'cover',
'dvcs',
+ 'organizations',
'wiki',
- 'toolbar',
- 'apiclient',
+ #'toolbar',
+ #'apiclient',
'email_mangler',
- 'build'
+ 'build',
+
+ 'django_forms_bootstrap',
+ 'forms_builder.forms',
+ 'fnpdjango',
)
-LOGIN_REDIRECT_URL = '/documents/user'
+LOGIN_REDIRECT_URL = '/'
-CAS_USER_ATTRS_MAP = {
- 'email': 'email', 'firstname': 'first_name', 'lastname': 'last_name'}
+#CAS_USER_ATTRS_MAP = {
+# 'email': 'email', 'firstname': 'first_name', 'lastname': 'last_name'}
# REPOSITORY_PATH = '/Users/zuber/Projekty/platforma/files/books'
IMAGE_DIR = 'images/'
-import djcelery
-djcelery.setup_loader()
+#import djcelery
+#djcelery.setup_loader()
BROKER_BACKEND = "djkombu.transport.DatabaseTransport"
BROKER_HOST = "localhost"
SHOW_APP_VERSION = False
+
+FORMS_BUILDER_EDITABLE_SLUGS = True
+FORMS_BUILDER_LABEL_MAX_LENGTH = 2048
+
+
+
try:
from redakcja.settings.compress import *
except ImportError:
},
'catalogue': {
'source_filenames': (
- 'css/filelist.css',
+ #'css/filelist.css',
+ 'css/base.css',
+ 'datepicker/css/datepicker.css',
),
'output_filename': 'compressed/catalogue_styles.css',
},
'js/catalogue/catalogue.js',
'js/slugify.js',
'email_mangler/email_mangler.js',
+ 'datepicker/js/bootstrap-datepicker.js',
),
'output_filename': 'compressed/catalogue_scripts.js',
},
--- /dev/null
+.overdue {
+ background-color: #A94442;
+}
+.box-overlay {
+ display: none;
+}
+.resource-box:hover .box-overlay {
+ display: block;
+}
+
+
+.popover {
+ color: #333;
+}
+
+
+
+
+
+.required-field:before {
+ content: "* ";
+}
--- /dev/null
+/*!
+ * Datepicker for Bootstrap
+ *
+ * Copyright 2012 Stefan Petre
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ */
+.datepicker {
+ top: 0;
+ left: 0;
+ padding: 4px;
+ margin-top: 1px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ /*.dow {
+ border-top: 1px solid #ddd !important;
+ }*/
+
+}
+.datepicker:before {
+ content: '';
+ display: inline-block;
+ border-left: 7px solid transparent;
+ border-right: 7px solid transparent;
+ border-bottom: 7px solid #ccc;
+ border-bottom-color: rgba(0, 0, 0, 0.2);
+ position: absolute;
+ top: -7px;
+ left: 6px;
+}
+.datepicker:after {
+ content: '';
+ display: inline-block;
+ border-left: 6px solid transparent;
+ border-right: 6px solid transparent;
+ border-bottom: 6px solid #ffffff;
+ position: absolute;
+ top: -6px;
+ left: 7px;
+}
+.datepicker > div {
+ display: none;
+}
+.datepicker table {
+ width: 100%;
+ margin: 0;
+}
+.datepicker td,
+.datepicker th {
+ text-align: center;
+ width: 20px;
+ height: 20px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.datepicker td.day:hover {
+ background: #eeeeee;
+ cursor: pointer;
+}
+.datepicker td.day.disabled {
+ color: #eeeeee;
+}
+.datepicker td.old,
+.datepicker td.new {
+ color: #999999;
+}
+.datepicker td.active,
+.datepicker td.active:hover {
+ color: #ffffff;
+ background-color: #006dcc;
+ background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+ background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+ background-image: linear-gradient(to bottom, #0088cc, #0044cc);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
+ border-color: #0044cc #0044cc #002a80;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ *background-color: #0044cc;
+ /* Darken IE7 buttons by default so they stand out more given they won't have borders */
+
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ color: #fff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.datepicker td.active:hover,
+.datepicker td.active:hover:hover,
+.datepicker td.active:focus,
+.datepicker td.active:hover:focus,
+.datepicker td.active:active,
+.datepicker td.active:hover:active,
+.datepicker td.active.active,
+.datepicker td.active:hover.active,
+.datepicker td.active.disabled,
+.datepicker td.active:hover.disabled,
+.datepicker td.active[disabled],
+.datepicker td.active:hover[disabled] {
+ color: #ffffff;
+ background-color: #0044cc;
+ *background-color: #003bb3;
+}
+.datepicker td.active:active,
+.datepicker td.active:hover:active,
+.datepicker td.active.active,
+.datepicker td.active:hover.active {
+ background-color: #003399 \9;
+}
+.datepicker td span {
+ display: block;
+ width: 47px;
+ height: 54px;
+ line-height: 54px;
+ float: left;
+ margin: 2px;
+ cursor: pointer;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.datepicker td span:hover {
+ background: #eeeeee;
+}
+.datepicker td span.active {
+ color: #ffffff;
+ background-color: #006dcc;
+ background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+ background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+ background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+ background-image: linear-gradient(to bottom, #0088cc, #0044cc);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
+ border-color: #0044cc #0044cc #002a80;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ *background-color: #0044cc;
+ /* Darken IE7 buttons by default so they stand out more given they won't have borders */
+
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+ color: #fff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.datepicker td span.active:hover,
+.datepicker td span.active:focus,
+.datepicker td span.active:active,
+.datepicker td span.active.active,
+.datepicker td span.active.disabled,
+.datepicker td span.active[disabled] {
+ color: #ffffff;
+ background-color: #0044cc;
+ *background-color: #003bb3;
+}
+.datepicker td span.active:active,
+.datepicker td span.active.active {
+ background-color: #003399 \9;
+}
+.datepicker td span.old {
+ color: #999999;
+}
+.datepicker th.switch {
+ width: 145px;
+}
+.datepicker th.next,
+.datepicker th.prev {
+ font-size: 21px;
+}
+.datepicker thead tr:first-child th {
+ cursor: pointer;
+}
+.datepicker thead tr:first-child th:hover {
+ background: #eeeeee;
+}
+.input-append.date .add-on i,
+.input-prepend.date .add-on i {
+ display: block;
+ cursor: pointer;
+ width: 16px;
+ height: 16px;
+}
\ No newline at end of file
--- /dev/null
+/* =========================================================
+ * bootstrap-datepicker.js
+ * http://www.eyecon.ro/bootstrap-datepicker
+ * =========================================================
+ * Copyright 2012 Stefan Petre
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+
+!function( $ ) {
+
+ // Picker object
+
+ var Datepicker = function(element, options){
+ this.element = $(element);
+ this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
+ this.picker = $(DPGlobal.template)
+ .appendTo('body')
+ .on({
+ click: $.proxy(this.click, this)//,
+ //mousedown: $.proxy(this.mousedown, this)
+ });
+ this.isInput = this.element.is('input');
+ this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
+
+ if (this.isInput) {
+ this.element.on({
+ focus: $.proxy(this.show, this),
+ //blur: $.proxy(this.hide, this),
+ keyup: $.proxy(this.update, this)
+ });
+ } else {
+ if (this.component){
+ this.component.on('click', $.proxy(this.show, this));
+ } else {
+ this.element.on('click', $.proxy(this.show, this));
+ }
+ }
+
+ this.minViewMode = options.minViewMode||this.element.data('date-minviewmode')||0;
+ if (typeof this.minViewMode === 'string') {
+ switch (this.minViewMode) {
+ case 'months':
+ this.minViewMode = 1;
+ break;
+ case 'years':
+ this.minViewMode = 2;
+ break;
+ default:
+ this.minViewMode = 0;
+ break;
+ }
+ }
+ this.viewMode = options.viewMode||this.element.data('date-viewmode')||0;
+ if (typeof this.viewMode === 'string') {
+ switch (this.viewMode) {
+ case 'months':
+ this.viewMode = 1;
+ break;
+ case 'years':
+ this.viewMode = 2;
+ break;
+ default:
+ this.viewMode = 0;
+ break;
+ }
+ }
+ this.startViewMode = this.viewMode;
+ this.weekStart = options.weekStart||this.element.data('date-weekstart')||0;
+ this.weekEnd = this.weekStart === 0 ? 6 : this.weekStart - 1;
+ this.onRender = options.onRender;
+ this.fillDow();
+ this.fillMonths();
+ this.update();
+ this.showMode();
+ };
+
+ Datepicker.prototype = {
+ constructor: Datepicker,
+
+ show: function(e) {
+ this.picker.show();
+ this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
+ this.place();
+ $(window).on('resize', $.proxy(this.place, this));
+ if (e ) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ if (!this.isInput) {
+ }
+ var that = this;
+ $(document).on('mousedown', function(ev){
+ if ($(ev.target).closest('.datepicker').length == 0) {
+ that.hide();
+ }
+ });
+ this.element.trigger({
+ type: 'show',
+ date: this.date
+ });
+ },
+
+ hide: function(){
+ this.picker.hide();
+ $(window).off('resize', this.place);
+ this.viewMode = this.startViewMode;
+ this.showMode();
+ if (!this.isInput) {
+ $(document).off('mousedown', this.hide);
+ }
+ //this.set();
+ this.element.trigger({
+ type: 'hide',
+ date: this.date
+ });
+ },
+
+ set: function() {
+ var formated = DPGlobal.formatDate(this.date, this.format);
+ if (!this.isInput) {
+ if (this.component){
+ this.element.find('input').prop('value', formated);
+ }
+ this.element.data('date', formated);
+ } else {
+ this.element.prop('value', formated);
+ }
+ },
+
+ setValue: function(newDate) {
+ if (typeof newDate === 'string') {
+ this.date = DPGlobal.parseDate(newDate, this.format);
+ } else {
+ this.date = new Date(newDate);
+ }
+ this.set();
+ this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
+ this.fill();
+ },
+
+ place: function(){
+ var offset = this.component ? this.component.offset() : this.element.offset();
+ this.picker.css({
+ top: offset.top + this.height,
+ left: offset.left
+ });
+ },
+
+ update: function(newDate){
+ this.date = DPGlobal.parseDate(
+ typeof newDate === 'string' ? newDate : (this.isInput ? this.element.prop('value') : this.element.data('date')),
+ this.format
+ );
+ this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
+ this.fill();
+ },
+
+ fillDow: function(){
+ var dowCnt = this.weekStart;
+ var html = '<tr>';
+ while (dowCnt < this.weekStart + 7) {
+ html += '<th class="dow">'+DPGlobal.dates.daysMin[(dowCnt++)%7]+'</th>';
+ }
+ html += '</tr>';
+ this.picker.find('.datepicker-days thead').append(html);
+ },
+
+ fillMonths: function(){
+ var html = '';
+ var i = 0
+ while (i < 12) {
+ html += '<span class="month">'+DPGlobal.dates.monthsShort[i++]+'</span>';
+ }
+ this.picker.find('.datepicker-months td').append(html);
+ },
+
+ fill: function() {
+ var d = new Date(this.viewDate),
+ year = d.getFullYear(),
+ month = d.getMonth(),
+ currentDate = this.date.valueOf();
+ this.picker.find('.datepicker-days th:eq(1)')
+ .text(DPGlobal.dates.months[month]+' '+year);
+ var prevMonth = new Date(year, month-1, 28,0,0,0,0),
+ day = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth());
+ prevMonth.setDate(day);
+ prevMonth.setDate(day - (prevMonth.getDay() - this.weekStart + 7)%7);
+ var nextMonth = new Date(prevMonth);
+ nextMonth.setDate(nextMonth.getDate() + 42);
+ nextMonth = nextMonth.valueOf();
+ var html = [];
+ var clsName,
+ prevY,
+ prevM;
+ while(prevMonth.valueOf() < nextMonth) {
+ if (prevMonth.getDay() === this.weekStart) {
+ html.push('<tr>');
+ }
+ clsName = this.onRender(prevMonth);
+ prevY = prevMonth.getFullYear();
+ prevM = prevMonth.getMonth();
+ if ((prevM < month && prevY === year) || prevY < year) {
+ clsName += ' old';
+ } else if ((prevM > month && prevY === year) || prevY > year) {
+ clsName += ' new';
+ }
+ if (prevMonth.valueOf() === currentDate) {
+ clsName += ' active';
+ }
+ html.push('<td class="day '+clsName+'">'+prevMonth.getDate() + '</td>');
+ if (prevMonth.getDay() === this.weekEnd) {
+ html.push('</tr>');
+ }
+ prevMonth.setDate(prevMonth.getDate()+1);
+ }
+ this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
+ var currentYear = this.date.getFullYear();
+
+ var months = this.picker.find('.datepicker-months')
+ .find('th:eq(1)')
+ .text(year)
+ .end()
+ .find('span').removeClass('active');
+ if (currentYear === year) {
+ months.eq(this.date.getMonth()).addClass('active');
+ }
+
+ html = '';
+ year = parseInt(year/10, 10) * 10;
+ var yearCont = this.picker.find('.datepicker-years')
+ .find('th:eq(1)')
+ .text(year + '-' + (year + 9))
+ .end()
+ .find('td');
+ year -= 1;
+ for (var i = -1; i < 11; i++) {
+ html += '<span class="year'+(i === -1 || i === 10 ? ' old' : '')+(currentYear === year ? ' active' : '')+'">'+year+'</span>';
+ year += 1;
+ }
+ yearCont.html(html);
+ },
+
+ click: function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ var target = $(e.target).closest('span, td, th');
+ if (target.length === 1) {
+ switch(target[0].nodeName.toLowerCase()) {
+ case 'th':
+ switch(target[0].className) {
+ case 'switch':
+ this.showMode(1);
+ break;
+ case 'prev':
+ case 'next':
+ this.viewDate['set'+DPGlobal.modes[this.viewMode].navFnc].call(
+ this.viewDate,
+ this.viewDate['get'+DPGlobal.modes[this.viewMode].navFnc].call(this.viewDate) +
+ DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1)
+ );
+ this.fill();
+ this.set();
+ break;
+ }
+ break;
+ case 'span':
+ if (target.is('.month')) {
+ var month = target.parent().find('span').index(target);
+ this.viewDate.setMonth(month);
+ } else {
+ var year = parseInt(target.text(), 10)||0;
+ this.viewDate.setFullYear(year);
+ }
+ if (this.viewMode !== 0) {
+ this.date = new Date(this.viewDate);
+ this.element.trigger({
+ type: 'changeDate',
+ date: this.date,
+ viewMode: DPGlobal.modes[this.viewMode].clsName
+ });
+ }
+ this.showMode(-1);
+ this.fill();
+ this.set();
+ break;
+ case 'td':
+ if (target.is('.day') && !target.is('.disabled')){
+ var day = parseInt(target.text(), 10)||1;
+ var month = this.viewDate.getMonth();
+ if (target.is('.old')) {
+ month -= 1;
+ } else if (target.is('.new')) {
+ month += 1;
+ }
+ var year = this.viewDate.getFullYear();
+ this.date = new Date(year, month, day,0,0,0,0);
+ this.viewDate = new Date(year, month, Math.min(28, day),0,0,0,0);
+ this.fill();
+ this.set();
+ this.element.trigger({
+ type: 'changeDate',
+ date: this.date,
+ viewMode: DPGlobal.modes[this.viewMode].clsName
+ });
+ }
+ break;
+ }
+ }
+ },
+
+ mousedown: function(e){
+ e.stopPropagation();
+ e.preventDefault();
+ },
+
+ showMode: function(dir) {
+ if (dir) {
+ this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
+ }
+ this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
+ }
+ };
+
+ $.fn.datepicker = function ( option, val ) {
+ return this.each(function () {
+ var $this = $(this),
+ data = $this.data('datepicker'),
+ options = typeof option === 'object' && option;
+ if (!data) {
+ $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
+ }
+ if (typeof option === 'string') data[option](val);
+ });
+ };
+
+ $.fn.datepicker.defaults = {
+ onRender: function(date) {
+ return '';
+ }
+ };
+ $.fn.datepicker.Constructor = Datepicker;
+
+ var DPGlobal = {
+ modes: [
+ {
+ clsName: 'days',
+ navFnc: 'Month',
+ navStep: 1
+ },
+ {
+ clsName: 'months',
+ navFnc: 'FullYear',
+ navStep: 1
+ },
+ {
+ clsName: 'years',
+ navFnc: 'FullYear',
+ navStep: 10
+ }],
+ dates:{
+ days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
+ daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
+ daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
+ months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+ },
+ isLeapYear: function (year) {
+ return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
+ },
+ getDaysInMonth: function (year, month) {
+ return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
+ },
+ parseFormat: function(format){
+ var separator = format.match(/[.\/\-\s].*?/),
+ parts = format.split(/\W+/);
+ if (!separator || !parts || parts.length === 0){
+ throw new Error("Invalid date format.");
+ }
+ return {separator: separator, parts: parts};
+ },
+ parseDate: function(date, format) {
+ var parts = date.split(format.separator),
+ date = new Date(),
+ val;
+ date.setHours(0);
+ date.setMinutes(0);
+ date.setSeconds(0);
+ date.setMilliseconds(0);
+ if (parts.length === format.parts.length) {
+ var year = date.getFullYear(), day = date.getDate(), month = date.getMonth();
+ for (var i=0, cnt = format.parts.length; i < cnt; i++) {
+ val = parseInt(parts[i], 10)||1;
+ switch(format.parts[i]) {
+ case 'dd':
+ case 'd':
+ day = val;
+ date.setDate(val);
+ break;
+ case 'mm':
+ case 'm':
+ month = val - 1;
+ date.setMonth(val - 1);
+ break;
+ case 'yy':
+ year = 2000 + val;
+ date.setFullYear(2000 + val);
+ break;
+ case 'yyyy':
+ year = val;
+ date.setFullYear(val);
+ break;
+ }
+ }
+ date = new Date(year, month, day, 0 ,0 ,0);
+ }
+ return date;
+ },
+ formatDate: function(date, format){
+ var val = {
+ d: date.getDate(),
+ m: date.getMonth() + 1,
+ yy: date.getFullYear().toString().substring(2),
+ yyyy: date.getFullYear()
+ };
+ val.dd = (val.d < 10 ? '0' : '') + val.d;
+ val.mm = (val.m < 10 ? '0' : '') + val.m;
+ var date = [];
+ for (var i=0, cnt = format.parts.length; i < cnt; i++) {
+ date.push(val[format.parts[i]]);
+ }
+ return date.join(format.separator);
+ },
+ headTemplate: '<thead>'+
+ '<tr>'+
+ '<th class="prev">‹</th>'+
+ '<th colspan="5" class="switch"></th>'+
+ '<th class="next">›</th>'+
+ '</tr>'+
+ '</thead>',
+ contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>'
+ };
+ DPGlobal.template = '<div class="datepicker dropdown-menu">'+
+ '<div class="datepicker-days">'+
+ '<table class=" table-condensed">'+
+ DPGlobal.headTemplate+
+ '<tbody></tbody>'+
+ '</table>'+
+ '</div>'+
+ '<div class="datepicker-months">'+
+ '<table class="table-condensed">'+
+ DPGlobal.headTemplate+
+ DPGlobal.contTemplate+
+ '</table>'+
+ '</div>'+
+ '<div class="datepicker-years">'+
+ '<table class="table-condensed">'+
+ DPGlobal.headTemplate+
+ DPGlobal.contTemplate+
+ '</table>'+
+ '</div>'+
+ '</div>';
+
+}( window.jQuery );
\ No newline at end of file
--- /dev/null
+/*!
+ * Datepicker for Bootstrap
+ *
+ * Copyright 2012 Stefan Petre
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ */
+
+.datepicker {
+ top: 0;
+ left: 0;
+ padding: 4px;
+ margin-top: 1px;
+ .border-radius(4px);
+ &:before {
+ content: '';
+ display: inline-block;
+ border-left: 7px solid transparent;
+ border-right: 7px solid transparent;
+ border-bottom: 7px solid #ccc;
+ border-bottom-color: rgba(0,0,0,.2);
+ position: absolute;
+ top: -7px;
+ left: 6px;
+ }
+ &:after {
+ content: '';
+ display: inline-block;
+ border-left: 6px solid transparent;
+ border-right: 6px solid transparent;
+ border-bottom: 6px solid @white;
+ position: absolute;
+ top: -6px;
+ left: 7px;
+ }
+ >div {
+ display: none;
+ }
+ table{
+ width: 100%;
+ margin: 0;
+ }
+ td,
+ th{
+ text-align: center;
+ width: 20px;
+ height: 20px;
+ .border-radius(4px);
+ }
+ td {
+ &.day:hover {
+ background: @grayLighter;
+ cursor: pointer;
+ }
+ &.day.disabled {
+ color: @grayLighter;
+ }
+ &.old,
+ &.new {
+ color: @grayLight;
+ }
+ &.active,
+ &.active:hover {
+ .buttonBackground(@btnPrimaryBackground, spin(@btnPrimaryBackground, 20));
+ color: #fff;
+ text-shadow: 0 -1px 0 rgba(0,0,0,.25);
+ }
+ span {
+ display: block;
+ width: 47px;
+ height: 54px;
+ line-height: 54px;
+ float: left;
+ margin: 2px;
+ cursor: pointer;
+ .border-radius(4px);
+ &:hover {
+ background: @grayLighter;
+ }
+ &.active {
+ .buttonBackground(@btnPrimaryBackground, spin(@btnPrimaryBackground, 20));
+ color: #fff;
+ text-shadow: 0 -1px 0 rgba(0,0,0,.25);
+ }
+ &.old {
+ color: @grayLight;
+ }
+ }
+ }
+
+ th {
+ &.switch {
+ width: 145px;
+ }
+ &.next,
+ &.prev {
+ font-size: @baseFontSize * 1.5;
+ }
+ }
+
+ thead tr:first-child th {
+ cursor: pointer;
+ &:hover{
+ background: @grayLighter;
+ }
+ }
+ /*.dow {
+ border-top: 1px solid #ddd !important;
+ }*/
+}
+.input-append,
+.input-prepend {
+ &.date {
+ .add-on i {
+ display: block;
+ cursor: pointer;
+ width: 16px;
+ height: 16px;
+ }
+ }
+}
\ No newline at end of file
});
+ var nowTemp = new Date();
+ var now = new Date(nowTemp.getFullYear(), nowTemp.getMonth(), nowTemp.getDate(), 0, 0, 0, 0);
+
+ $('.datepicker-field').each(function() {
+ var checkout = $(this).datepicker({
+ format: 'yyyy-mm-dd',
+ weekStart: 1,
+ onRender: function(date) {
+ return date.valueOf() < now.valueOf() ? 'disabled' : '';
+ },
+ }).on('changeDate', function(ev) {
+ checkout.hide();
+ }).data('datepicker');
+ });
+
+
+ $("select").change(function() {
+ var helpdiv = $(this).next();
+ if (helpdiv.hasClass('help-text')) {
+ var helptext = $("option:selected", this).attr('data-help');
+ helpdiv.html($("option:selected", this).attr('data-help') || '');
+ }
+ });
+
+
+
+ // tutorial mode
+ var tutorial;
+ var start;
+
+ var first_reset = true;
+ function tutreset() {
+ if (start) $(start).popover('hide');
+ start = null;
+
+ tutorial = $.makeArray($('[data-toggle="tutorial"]').sort(
+ function(a, b) {return $(a).attr('data-tutorial') < $(b).attr('data-tutorial') ? -1 : 1}
+ ));
+
+ if (first_reset) {
+ $.each(tutorial, function(i, e) {
+ var but = (i < tutorial.length - 1) ? '>>' : 'OK';
+ $(e).popover({
+ title: 'Tutorial',
+ trigger: 'focus',
+ template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div><div><a class="btn btn-default tutorial-off" href="#-" id="tutoff'+i+'">×</a><a style="float:right" class="btn btn-default tutorial-next" href="#-" id="nt'+i+'">' + but + '</a></div></div>'
+ }).on('shown.bs.popover', function () {
+ if (!$(e).data('tut-yet')) {
+ $("#tutoff"+i).on('click', tutoff);
+ $("#nt"+i).on('click', tut);
+ $(e).data('tut-yet', 'yes');
+ }
+ });
+ //$(start).on('hide.bs.popover', tut);
+ });
+ first_reset = false;
+ }
+ }
+
+ function tuton() {
+ sessionStorage.setItem("tutorial", "on");
+ tutreset();
+ $('#tutModal').modal('show');
+ return false;
+ }
+ function tutoff() {
+ if (start) $(start).popover('hide');
+ start = null;
+ sessionStorage.removeItem("tutorial");
+ return false;
+ }
+ function tut() {
+ if (start) $(start).popover('hide');
+ if (tutorial.length) {
+ start = tutorial.shift();
+ $(start).popover('show');
+ //~ if (!$(start).data('tut-yet')) {
+ //~ $(".popover .tutorial-off").on('click', tutoff);
+ //~ $(".popover .tutorial-next").on('click', tut);
+ //~ $(start).data('tut-yet', 'yes');
+ //~ }
+ }
+ else {
+ start = null;
+ }
+ return false;
+ }
+ $('#tutModal').on('hidden.bs.modal', tut);
+
+ if (sessionStorage.getItem("tutorial") == "on" && $("#tuton").length == 0) {
+ tutreset();
+ tut();
+ }
+ $("#tuton").on('click', tuton);
+
+
});
})(jQuery);
{% load i18n %}
-{% block content %}
+{% block inner_content %}
<h1>{% trans "Page not found" %}</h1>
</p>
-{% endblock content %}
+{% endblock %}
{% block "content" %}
-<h1>Błąd po stronie serwera.</h1>
+<h1>Internal Server Error</h1>
-<p>Niestety nasz serwer WWW nie był w stanie dostarczyć Ci strony o którą prosisz.</p>
-<p><b>Serdecznie przepraszamy.</b></p>
-<p>Administrator został już powiadomiony o błędzie, ale jeśli chciałbyś
-przekazać nam więcej informacji na temat błędu, napisz na <a href="mailto:radoslaw.czajka@nowoczesnapolska.org.pl">nasz adres</a>.</p>
+<p>Unfortunately our WWW server was unable to deliver the page you asked for. We're sorry for the inconvenience.</p>
+<p>The administrator has been already notified of the error, but if you'd like to provide more information, write to <a href="mailto:radekczajka@mdrn.pl">our e-mail</a>.</p>
</div>
{% endblock %}
{% block "content" %}
-<h1>Serwis tymczasowo niedostępny</h1>
+<h1>Service temporarily unavailable</h1>
<p>
-Platfroma redakcyjna serwisu <a href="http://wolnelektury.pl/">Wolne Lektury</a> jest
-tymczasowo niedostępna z powodu prac administracyjnych.
+The service is unavailable due to maintanance.
</p>
-<p>Prosimy o wyrozumiałość i ponowne odwiedziny.</p>
+<p>Please try again later.</p>
{% endblock "content" %}
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
- <style type="text/css">
-
-body {
- margin: 0;
- font-family: verdana, sans-serif;
- font-size: 10px;
-}
-
-img {
- border: 0;
-}
-
-.clr {
- clear: both;
-}
-
-#tabs-nav {
- padding: 5px 5px 0 10px;
- background: #ffdfbf;
- border-bottom: 1px solid #ff8000;
- position: relative;
- height: 25px;
-}
-
-#logo {
- position: absolute;
- bottom: 0;
-}
-
-#content {
- padding: 10px;
-}
-
-a, a:visited, a:active {
- color: #bf6000;
- text-decoration: none;
-}
-
-a:hover {
- text-decoration: underline;
-}
-
- </style>
- <title>Platforma Redakcyjna</title>
+ <title>MIL/PEER</title>
</head>
<body>
-<div id="tabs-nav">
-
-<img id="logo" src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAcCAYAAADxyeavAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAD7AAAA+wBSobVeQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAATYSURBVFiFvdhbjN1VFcfxzzpTR8tUYisJViJWJCCIVKUNYCGdaRPUMBYvqNFSYkyol4iNsalpaOfPf4bLKBiR8kTQCCG0tWMBa4WEgLY0lJBga61FiGKRahPqgxamUGjP8uF/Mr0wlzMHpzvZL2ft9Vvfvfb6r71zIjOd8FHGe/GgMKAnf9iqTO3/iNTcuDHeh02YJS1+K1InFr6MGQ7ZjDbcgDOUcW6rcicOvowzVBk/bJK52t2Ow+huVfLEwB8NTqfr8kXLcx+24DOtyk48fBkn4zeom6RLkXuGbOF+XOzmeHcr0hMLX0YN92EqulyXLxwX/WG0eV1XK/ITnfmb8Ul8UZHPv8m6Ip/FP2kNftJbQhttlPElLMMSRW4ZZeWjmNdKiInJfBkz8HPco8jbx1j9KD6kjPeMN8zEwIcL0GGy7zSx+rGGz7hLpzX4MmrKmDaK6g5w0IwxtcLZqGNkvRHDtDYW43llnDes9bC/4YC6D4+q0hcfkX6Fp53kF+OFaA3+7dbi39jgpjj1TfYi69jJKPA3xmnqNuIl7S63NAeVcW7je2lqjN5tyngXOjG/MUPNVVbm08pYgC3esFUZCxS58zjvHRj+3fKTmOyQDWjXZq7luU9vLMRduAffaAb+uMxH6IvZyuhTxlOq7N6PL2M76uoeU0anIndpMxuv4AlldB4rZbuRMr/fz3A2uq3Iv+uNa6R78XuTLW0GHCKv1y50SVdgAU7Da3hceER6ROGPZOqPqQ7aiI/hK4p8wK3RYdAGXIwvKPK3oIxZeNI0Ha7Ng0MRy1imurw+p8hf64sL1W3GeixS5KHxwG/DR/Gy6g2yXoeHLM3BYT3KOAkDqptziSLvaPy2EXOEhXpynTvjbfbajwsVuaPh+6nGuu8r8jZlnKV6sL2ATkW+1iw4Vdk8XG3DckV+VZEDI4JDkQdMd4WqPlcp4xaFV3XoxlZptTK6Lc438AfRKJ0yTsG9WK/I29wQH1T1+P1YMF5wiMykjDvwLeEqPbm6ae8yvo2fYgO+psNhgx5SldWlWIQDilypjLsxBx9X9fTNCMxR5D/GC34EXoTSXbhaVcsDTSv0xVx1A/gvrsRf8Tu0q1ms7gPYg8cxGy+pSmUKLlXkX1oBZ6jbZOIarMFqZVzZtMLK3GSSWXgRW/FdYQ3OV7dXkWtU38duHFKVSjvmjwhexjvcFNPHCh3H/HuwLtrsch8+j4WK/GXTm6iCfh23qt7vT5hmnmvzYOOFubax6k/oHiqVMk4RZkszMRPnq9pooF9hRSO5Y8BXYjX8GEuEUo/ekZxH2EA7zsJzinx96Pfe6JImq9krfUK6CBfhTNWJPIc/q27mncL7pVvwoKqFHhgb/gjEN7FK9TFerchXjrNPUd2g56kuo2qGpXpyreFG1T43YV8DsJp1zx6z0SMxLlOV8m5VR9pztHlkeOiN+dI67BZWSeccBXo6ErvwpGjMulOFmdJaRf7rKJB5+I/CtnGe5Jmq7E/FZxX5VHPwR5wHcI7qWLcPzcm2WZYvN9a1q3r/IgziGUXObiThB1J/Q3GLNoutyGea3sCP4p1etREX4NOK3NwcfAVWM11b4+IZzn4yHsAlqnZ7Or6nyOkN+wZMUbNMXT8uEa7Xo7/pU6ieIdUGai6zMrf+DykEtvGDDlIUAAAAAElFTkSuQmCC' />
-
- <div class='clr' ></div>
-</div>
-
-<div id="content">
{% block "content" %}
{% endblock %}
-</div>
</body>
</html>
+
+
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load fnp_markup %}
+{% load flat_lang %}
+
+{% block title %}
+{% flat_lang flatpage as flatpagelang %}
+{{ flatpagelang.title }} | {{ block.super }}{% endblock %}
+
+{% block inner_content %}
+{% flat_lang flatpage as flatpagelang %}
+<h1>{{ flatpagelang.title }}</h1>
+
+{{ flatpagelang.content|textile_pl }}
+
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+
+{% block inner_content %}
+
+{% load forms_builder_tags %}
+{% render_built_form form %}
+
+{% endblock %}
+
--- /dev/null
+{% extends "catalogue/base.html" %}
+
+{% block inner_content %}
+
+<h1>{{ form.title }}</h1>
+{% if form.response %}
+<p>{{ form.response }}</p>
+{% endif %}
+{% endblock %}
+
--- /dev/null
+{% load bootstrap_tags %}
+{% load fnp_markup %}
+
+<h1>{{ form.title }}</h1>
+
+{% if form.intro %}
+<p>{{ form.intro|textile_pl }}</p>
+{% endif %}
+
+ {{ form_for_form.media }}
+ <form action="{{ form.get_absolute_url }}" method="post"
+ {% if form_for_form.is_multipart %}enctype="multipart/form-data"{% endif %}>
+ {% csrf_token %}
+ {{ form_for_form|as_bootstrap }}
+ <input type="submit" class="btn btn-default" value="{{ form.button_text }}">
+ </form>
+
+
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+{% load thumbnail %}
+{% load catalogue_files %}
+
+
+{% block content %}
+
+<div class="jumbotron" style="margin-top: -20px">
+<div class="row">
+<div class="col-md-8 col-md-offset-2">
+
+ <h1><span style="color: #dd8000">MIL</span>/<span style="color: #0a0">PEER</span>
+ </h1>
+
+<div class="row">
+
+<div class="col-md-6">
+
+{% blocktrans %}
+ <p>Media & Information Literacy<br>
+ Platform for Exchanging<br>
+ Educational Resources<br>
+ </p>
+{% endblocktrans %}
+ <a class="btn btn-success btn-lg" id="tuton">{% trans "Create and share your resources<br>Start tutorial" %}</a>
+</div>
+<div class="col-md-6">
+ <a href="/competition/" class="btn btn-success btn-lg">{% trans "Competition" %}</a>
+ <p style="margin-top: 17px;">{% trans "Joint project by:" %}</p>
+ <a target="_blank" href="http://evensfoundation.be"><img style="margin-top: 20px; width: 145px;margin-right: 40px;" src="http://evensfoundation.be/sites/all/themes/evens/images/logo2.png"></a>
+ <a target="_blank" href="https://nowoczesnapolska.org.pl"><img style="margin-top: 20px; width: 135px;" src="//nowoczesnapolska.org.pl/wp-content/themes/koed/images/nowoczesnapolska.org.pl.png"></a>
+</div>
+</div>
+</div>
+
+</div>
+</div>
+
+{{ block.super }}
+{% endblock %}
+
+{% block inner_content %}
+
+
+<section>
+<h1>{% trans "See active organizations and join" %}</h1>
+{% for org in organizations %}
+ <div style="width: 182px; height: 180px; position: relative; border: 1px solid #aaa; margin: 10px; display: inline-block; padding: 0px; vertical-align: bottom">
+ <a style='display:block' href="{% url 'organizations_main' org.pk %}">
+ {% if org.logo %}
+ {% thumbnail org.logo "160x100" format="PNG" padding=True as th %}
+ <img src="{{ th.url }}" style="margin: 10px;">
+ {% endthumbnail %}
+ {% endif %}
+ <div style="position: absolute; bottom: 10px; left: 10px; width: 160px; height: 40px; overflow:hidden">{{ org.name }}</div>
+ </a>
+ </div>
+{% endfor %}
+{% if more_organizations %}
+<p style="text-align: right"><a href="{% url 'organizations' %}">{% trans "More organizations" %}</a></p>
+{% endif %}
+</section>
+
+
+
+
+<section>
+<h1>{% trans "Finished resources" %}</h1>
+{% for doc in finished %}
+ <div class="resource-box" style="width: 182px; height: 180px; position: relative; border: 1px solid #aaa; margin: 10px; display: inline-block; padding: 0px; vertical-align: bottom">
+ <a style='display:block' href="{% url 'catalogue_html' doc.pk %}">
+ {% if doc.meta.cover_url %}
+ {% thumbnail doc.meta.cover_url|as_media_for:doc "180x120" crop="center" format="PNG" as th %}
+ <img src="{{ th.url }}">
+ {% endthumbnail %}
+ {% elif doc.owner_organization.logo %}
+ {% thumbnail doc.owner_organization.logo "160x100" format="PNG" padding=True as th %}
+ <img src="{{ th.url }}">
+ {% endthumbnail %}
+ {% endif %}
+ <div style="position: absolute; bottom: 10px; left: 10px; width: 160px; height: 40px; overflow:hidden">{{ doc.meta.title }}</div>
+ <div class="box-overlay" style="position: absolute; top:0; left: 0; padding: 5px; width: 180px; height: 120px; background: black; color: white; opacity: .7">
+ {% if doc.meta.audience %}
+ {% trans "Audience" %}: {{ doc.meta.audience }}<br>
+ {% endif %}
+ {% trans "Owner" %}: {{ doc.owner_organization|default:"" }}
+ {{ doc.owner_user.first_name }} {{ doc.owner_user.last_name }}<br/>
+ </div>
+ </a>
+ </div>
+{% endfor %}
+{% if more_finished %}
+<p style="text-align: right"><a href="{% url 'catalogue_finished' %}">{% trans "More finished resources" %}</a></p>
+{% endif %}
+</section>
+
+<section>
+<h1>{% trans "Upcoming resources" %}</h1>
+{% for doc in upcoming %}
+ <div class="resource-box" style="width: 182px; height: 180px; position: relative; border: 1px solid #aaa; margin: 10px; display: inline-block; padding: 0px; vertical-align: bottom">
+ <a style='display:block' href="{% url 'catalogue_preview' doc.pk %}">
+ {% if doc.meta.cover_url %}
+ {% thumbnail doc.meta.cover_url|as_media_for:doc "180x120" crop="center" format="PNG" as th %}
+ <img src="{{ th.url }}">
+ {% endthumbnail %}
+ {% elif doc.owner_organization.logo %}
+ {% thumbnail doc.owner_organization.logo "160x100" format="PNG" padding=True as th %}
+ <img src="{{ th.url }}" style="margin: 10px;">
+ {% endthumbnail %}
+ {% endif %}
+ <div style="position: absolute; bottom: 10px; left: 10px; width: 160px; height: 40px; overflow:hidden">{{ doc.meta.title }}</div>
+ <div class="box-overlay" style="position: absolute; top:0; left: 0; padding: 5px; width: 180px; height: 120px; background: black; color: white; opacity: .7">
+ {% if doc.meta.audience %}
+ {% trans "Audience" %}: {{ doc.meta.audience }}<br>
+ {% endif %}
+ {% trans "Owner" %}: {{ doc.owner_organization|default:"" }}
+ {{ doc.owner_user.first_name }} {{ doc.owner_user.last_name }}<br/>
+ </div>
+ </a>
+ </div>
+{% endfor %}
+{% if more_upcoming %}
+<p style="text-align: right"><a href="{% url 'catalogue_upcoming' %}">{% trans "More upcoming resources" %}</a></p>
+{% endif %}
+</section>
+
+
+
+
+<div id="tutModal" class="modal fade bs-example-modal-sm" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
+ <div class="modal-dialog modal-sm">
+ <div class="modal-content">
+ <div class="modal-header"><h4>{% trans "Tutorial mode" %}</h4></div>
+ <div class="modal-body"><p>{% trans "Tutorial will help you navigate through the platform. You can disable it at any point." %}</p></div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">OK</button>
+ </div>
+ </div>
+ </div>
+</div>
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+
+{% block content %}
+<div class="row">
+<div class="col-md-6 col-md-offset-3">
+
+ <h1 style="margin-bottom:1em">{% trans "Register" %}</h1>
+
+
+ <form enctype="multipart/form-data" method="POST">
+ {% csrf_token %}
+ {{ form.non_field_errors }}
+ <label for="title">{% trans "First name" %}</label>
+ {{ form.first_name.errors }}
+ <input class="form-control" name="first_name" type="text">
+ <label for="title">{% trans "Last name" %}</label>
+ {{ form.last_name.errors }}
+ <input class="form-control" name="last_name" type="text">
+ <label for="title">{% trans "E-mail" %}</label>
+ {{ form.email.errors }}
+ <input class="form-control" name="email" type="email">
+ <label for="title">{% trans "Your new password" %}</label>
+ {{ form.password.errors }}
+ <input class="form-control" name="password" type="password">
+
+ <button style="margin-top:1em;" class="btn btn-default" type="submit">{% trans "Register" %}</button></td></tr>
+ </form>
+
+</div>
+</div>
+{% endblock content %}
{% load i18n %}
+<ul class="nav navbar-nav navbar-right">
+
+
+<li><a style="color: white">
+ <form action="{% url 'django.views.i18n.set_language' %}" method="post" style="display: inline">
+ {% csrf_token %}
+ <input type="hidden" name="language" value="en" /><button type="submit" style="border: 0; background: none">en</button></form> |
+ <form action="{% url 'django.views.i18n.set_language' %}" method="post" style="display: inline">
+ {% csrf_token %}
+ <input type="hidden" name="language" value="pl" /><button type="submit" style="border: 0; background: none">pl</button></form> |
+</a>
+</li>
+
+<li><a href="/platform/" style="color: white">{% trans "About" %}</a></li>
+<li><a href="/competition/" style="color: white">{% trans "Competition" %}</a></li>
+<li><a href="{% url 'organizations_new' %}" style="color: white"
+ {% if request.path == '/' %}
+ data-toggle="tutorial" data-tutorial="1" data-placement="bottom"
+ data-content='{% trans "Create an organization here. Organizations help manage team's work." %}'
+ {% endif %}
+>{% trans "New organization" %}</a></li>
+<li><a href="{% url 'catalogue_create_missing' %}" style="color: white"
+ {% if request.path == '/' %}
+ data-toggle="tutorial" data-tutorial="2" data-placement="bottom"
+ data-content="{% trans 'Start editing a new resource.' %}"
+ {% endif %}
+>{% trans "New resource" %}</a></li>
+
{% if user.is_authenticated %}
-<span class="user_name">{{ user.username }}</span> |
+<li><a href="{% url 'catalogue_user' %}" style="color: white">{% trans "My resources" %}</a></li>
+
+ <li class="dropdown">
+ <style>
+ #main-navbar .dropdown.open > a {
+ background: #888
+ }
+ </style>
+ <a href="#" class="dropdown-toggle" type="button" data-toggle="dropdown" style="color: white">
+ {{ user.username }}
+ <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu" style="">
+ {% if user.membership_set.all.exists %}
+ <li role="presentation" class="dropdown-header">{% trans "My organizations" %}:</li>
+ {% for membership in user.membership_set.all %}
+ <li><a href="{% url 'organizations_main' membership.organization.pk %}">{{ membership.organization }}</a></li>
+ {% endfor %}
+ {% else %}
+ <li role="presentation" class="dropdown-header">{% trans "You are not a member of any organizations" %}</li>
+ {% endif %}
+ <li><a href="{% url 'organizations_new' %}">{% trans "New organization" %}</a></li>
+ <li role="presentation" class="divider"></li>
+ <li><a href="{% url 'organizations_user_edit' %}">{% trans "Edit my data" %}</a></li>
+ {% if user.is_staff %}
+ <li><a href="{% url 'admin:index' %}">{% trans "Admin" %}</a></li>
+ {% endif %}
+ <li><a href="{% url 'logout' %}">{% trans "Logout" %}</a></li>
+ </ul>
+ </div>
+ </li>
-{% if user.is_staff %}
- <a href="{% url 'admin:index' %}">{% trans "Admin" %}</a> |
-{% endif %}
-<a href='{% url "logout" %}{% if logout_to %}?next={{ logout_to }}{% endif %}'>{% trans "Log Out" %}</a>
{% else %}
{% url "login" as login_url %}
{% ifnotequal login_url request.path %}
- <a href='{{ login_url }}'>{% trans "Log In" %}</a>
+ <li class="dropdown"
+ data-toggle="tutorial" data-tutorial="0" data-placement="bottom"
+ data-content="{% trans 'First, you will need an account.' %}"
+ >
+ <button class="btn btn-default navbar-btn dropdown-toggle" type="button" data-toggle="dropdown" style=""
+ >
+ {% trans "Log in / Register" %}
+ </button>
+ <div class="dropdown-menu" style="padding:0;margin-top:-5px; border:none;box-shadow:none; min-width:240px">
+ <div class="panel panel-default" style="color: #333;">
+ <div class="panel-heading">
+ <h3 class="panel-title">{% trans "Log in / Register" %}</h3>
+ </div>
+ <div class="panel-body">
+ <form method="POST" action="{% url 'login' %}">
+ {% csrf_token %}
+ <label for="email">
+ {% trans "E-mail" %}</label>
+ <input name="username" class="form-control" type="email">
+
+ <label for="password">
+ {% trans "Password" %}</label>
+ <input name="password" class="form-control" type="password">
+
+
+ <!--input type="checkbox" name="keep">
+ <label for="keep">Keep me logged in</label-->
+
+ <div><button class="btn btn-default">
+ {% trans "Log in" %}
+ </button>
+ </div>
+ </form>
+ <p><a href="{% url 'password_reset' %}">{% trans "I forgot my password" %}</a></p>
+ <hr>
+ <p style="margin-bottom:1em">{% trans "<strong>Register now</strong> to start editing your own materials." %}</p>
+ <a class="btn btn-default" href="{% url 'register' %}">{% trans "Register" %}</a>
+ </div>
+ </div>
+ </li>
{% endifnotequal %}
{% endif %}
+
+</ul>
+
--- /dev/null
+{% extends "catalogue/base.html" %}
+
+{% block inner_content %}
+
+<h1>Logged out</h1>
+
+<p>You are now logged out. <a href="/">Continue to the main page</a>.</p>
+
+{% endblock %}
\ No newline at end of file
{% block subtitle %} - Logowanie {% endblock subtitle %}
-{% block content %}
+{% block inner_content %}
+
+
-<div class="isection">
<form method="POST" action="{% url 'login' %}">
{% csrf_token %}
-{{ form.as_p }}
-<p><input type="submit" value="Login" /></p>
-<input type="hidden" name="next" value="{{ next|urlencode }}" />
-</form>
-</div>
-{% endblock content %}
+ <form method="POST" action="{% url 'login' %}">
+ {% csrf_token %}
+ {{ form.non_field_errors }}
+ <label for="email">
+ E-mail</label>
+ {{ form.username.errors }}
+ <input name="username" class="form-control" type="email">
+
+ <label for="password">
+ Password</label>
+ {{ form.password.errors }}
+ <input name="password" class="form-control" type="password">
+
+
+ <input type="checkbox" name="keep">
+ <label for="keep">Keep me logged in</label>
+
+ <div><button class="btn btn-default">
+ Log in
+ </button>
+ </div>
+ </form>
+ <p><a href="{% url 'password_reset' %}">I forgot my password</a></p>
+ <hr>
+ <a class="btn btn-default" href="{% url 'register' %}">Register</a>
+
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+
+{% block inner_content %}
+<h1>{{ title }}</h1>
+
+<p>{% trans "Your password has been set. You may go ahead and log in now." %}</p>
+
+<p><a href="{{ login_url }}">{% trans 'Log in' %}</a></p>
+
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+{% load bootstrap_tags %}
+
+{% block inner_content %}
+<h1>{{ title }}</h1>
+
+{% if validlink %}
+
+<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
+
+<form action="" method="post">{% csrf_token %}
+{{ form|as_bootstrap }}
+<input type="submit" class="btn btn-default" value="{% trans 'Change my password' %}">
+</form>
+
+{% else %}
+
+<p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p>
+
+{% endif %}
+
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+
+{% block inner_content %}
+<h1>{{ title }}</h1>
+
+<p>{% trans "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly." %}</p>
+
+<p>{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}</p>
+
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+
+{% block inner_content %}
+
+<h1 style="margin-bottom:1em">Reset password</h1>
+
+<form action="" method="post">{% csrf_token %}
+{{ form.email.errors }}
+<label for="id_email">Email</label>
+<input type="email" class="form-control" name="email">
+<button style="margin-top:1em" type="submit" class="btn btn-default">Reset my password</button>
+</form>
+
+
+{% endblock %}
\ No newline at end of file
from django.conf.urls.static import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.views.generic import RedirectView
+import forms_builder.forms.urls
admin.autodiscover()
urlpatterns = patterns('',
# Auth
- url(r'^accounts/login/$', 'django_cas.views.login', name='login'),
- url(r'^accounts/logout/$', 'django_cas.views.logout', name='logout'),
- url(r'^admin/login/$', 'django_cas.views.login', name='login'),
- url(r'^admin/logout/$', 'django_cas.views.logout', name='logout'),
+ #url(r'^accounts/login/$', 'django_cas.views.login', name='login'),
+ #url(r'^accounts/logout/$', 'django_cas.views.logout', name='logout'),
+ #url(r'^accounts/login/$', 'django.contrib.auth.views.login', name='login'),
+ #url(r'^accounts/logout/$', 'django.contrib.auth.views.login', name='logout'),
+ #url(r'^admin/login/$', 'django_cas.views.login', name='login'),
+ #url(r'^admin/logout/$', 'django_cas.views.logout', name='logout'),
+ url('^accounts/', include('django.contrib.auth.urls')),
# Admin panel
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
(r'^admin/', include(admin.site.urls)),
- (r'^comments/', include('django.contrib.comments.urls')),
-
- url(r'^$', RedirectView.as_view(url= '/documents/')),
+ url(r'^$', 'redakcja.views.main'),
+ url(r'^register$', 'redakcja.views.register', name='register'),
url(r'^documents/', include('catalogue.urls')),
- url(r'^apiclient/', include('apiclient.urls')),
url(r'^editor/', include('wiki.urls')),
url(r'^cover/', include('cover.urls')),
+ url(r'^organizations/', include('organizations.urls')),
+ url(r'^forms/', include(forms_builder.forms.urls)),
+
+ url(r'^i18n/', include('django.conf.urls.i18n')),
(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', dict(packages=['wiki'])),
)
--- /dev/null
+from django.shortcuts import render, redirect
+from django.contrib.auth import authenticate, login
+from django.contrib.auth.models import User
+from django.core.mail import send_mail
+from .forms import RegistrationForm
+from catalogue.models import Document
+from organizations.models import Organization
+
+def main(request):
+ upcoming = Document.objects.filter(deleted=False).filter(publish_log=None)
+ finished = Document.objects.filter(deleted=False).exclude(publish_log=None)
+ organizations = Organization.objects.all()
+ more_upcoming = upcoming.count() > 8
+ more_finished = finished.count() > 8
+ more_organizations = organizations.count() > 8
+ upcoming = upcoming[:8]
+ finished = finished[:8]
+ organizations = organizations[:8]
+
+
+ return render(request, 'main.html', {
+ 'finished': finished,
+ 'upcoming': upcoming,
+ 'organizations': organizations,
+ 'more_upcoming': more_upcoming,
+ 'more_finished': more_finished,
+ 'more_organizations': more_organizations,
+ })
+
+def register(request):
+ if request.method == 'POST':
+ form = RegistrationForm(request.POST, request.FILES)
+ if form.is_valid():
+ u = User.objects.create(
+ username=form.cleaned_data['email'],
+ first_name=form.cleaned_data['first_name'],
+ last_name=form.cleaned_data['last_name'],
+ email=form.cleaned_data['email']
+ )
+ u.set_password(form.cleaned_data['password'])
+ u.save()
+ login(request, authenticate(username=form.cleaned_data['email'], password=form.cleaned_data['password']))
+ send_mail('Registered at MIL/PEER',
+'''You have been successfully registered at MIL/PEER with this e-mail address.
+
+Thank you.
+
+--
+MIL/PEER team.''', 'milpeer@mdrn.pl', [form.cleaned_data['email']])
+ return redirect('/')
+ else:
+ form = RegistrationForm()
+ return render(request, 'registration.html', {'form': form})
--i http://pypi.nowoczesnapolska.org.pl/simple
+-i https://py.mdrn.pl:8443/simple
## Python libraries
lxml>=2.2.2
-Mercurial>=2.6,<2.7
+Mercurial>=3.3,<3.4
PyYAML>=3.0
Pillow
oauth2
# git+git://github.com/fnp/librarian.git@master#egg=librarian
## Django
-Django>=1.5,<1.6
-django-pipeline>=1.2,<1.3
-django_cas>=2.1,<2.2
-sorl-thumbnail>=11.09,<12
-django-maintenancemode>=0.9
+Django>=1.8,<1.9
+fnpdjango>=0.2,<0.3
+django-pipeline>=1.4.7,<1.5
+sorl-thumbnail>=12.2,<13
+#django-maintenancemode>=0.9
django-pagination
-django-gravatar
-django-celery
-django-kombu
-fnpdjango
+django-gravatar2
+python-slugify
+django-forms-bootstrap
+jsonfield
-# migrations
-south>=0.6
+celery>=3.1.12,<3.2
+kombu>3.0,<3.1
+
+South>=1.0.2
+
+Embeder
+
+future